【问题标题】:Preventing 'PersistentObjectException'防止“PersistentObjectException”
【发布时间】:2014-12-20 16:22:39
【问题描述】:

我有一个非常基本的 JAX-RS 服务(下面的 BookService 类),它允许创建 Book 类型的实体(也在下面)。 POST有效载荷

{
    "acquisitionDate": 1418849700000,
    "name": "Funny Title",
    "numberOfPages": 100
}

成功持久化Book 并返回201 CREATED。但是,包含id 属性以及负载上的任何非空值都会触发带有消息detached entity passed to persistorg.hibernate.PersistentObjectException我明白这意味着什么,并且在创建对象时(在这种情况下)在有效负载上包含 id 是没有意义的。但是,我更愿意防止此异常一直冒泡,并在这种情况下向我的用户呈现 400 BAD REQUEST (或者,至少完全忽略该属性)。但是,有两个主要问题:

  1. 到达create 的异常是EJBTransactionRolledbackException,我必须一直爬到堆栈跟踪才能发现根本原因;
  2. 根本原因是 org.hibernate.PersistentObjectException - 我正在部署到使用 Hibernate 的 Wildfly,但我想保持我的代码可移植,所以我真的不想捕获这个特定的异常。

据我了解,有两种可能的解决方案:

  1. bookRepo.create(book) 之前使用book.setId(null)。这将忽略 id 属性带有值并继续请求这一事实。
  2. 检查book.getId() != null 是否抛出类似IllegalArgumentException 的东西,可以映射到400 状态码。似乎是更好的解决方案。

但是,来自其他框架(例如 Django Rest Framework),我真的更希望由框架本身来处理这个问题……我的问题是,是否有任何内置方法可以实现这种行为我可能会失踪吗?

这是BookService 类:

@Stateless
@Path("/books")
public class BookService {
    @Inject
    private BookRepo bookRepo;

    @Context
    UriInfo uriInfo;

    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(@Valid Book book) {
        bookRepo.create(book);
        return Response.created(getBookUri(book)).build();
    }

    private URI getBookUri(Book book) {
        return uriInfo.getAbsolutePathBuilder()
                .path(book.getId().toString()).build();
    }
}

这是Book 类:

@Entity
@Table(name = "books")
public class Book {
    @Column(nullable = false)
    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    private Date acquisitionDate;

    @Column(nullable = false, updatable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Integer id;

    @Column(nullable = false)
    @NotNull
    @Size(max = 255, min = 1)
    private String name;

    @Column(nullable = false)
    @Min(value = 1)
    @NotNull
    private Integer numberOfPages;

    (getters/setters/...)
}

这是BookRepo 类:

@Stateless
public class BookRepo {
    @PersistenceContext(unitName = "book-repo")
    protected EntityManager em;

    public void create(Book book) {
        em.persist(book);
    }
}

【问题讨论】:

    标签: java jakarta-ee jpa jax-rs


    【解决方案1】:

    我不知道这是否真的是您正在寻找的答案,但我只是在玩弄这个想法并实施了一些东西。

    JAX-RS 2 规范定义了一个 bean 验证模型,所以我想也许你可以利用它。所有错误的验证都将映射到 400。您说 “我希望防止此异常一直冒泡并向我的用户展示,例如 400 BAD REQUEST”,但是如果验证不好,无论如何你都会得到。因此,无论您计划处理验证异常(如果有的话),都可以在此处执行相同操作。

    基本上我只是创建了一个约束注释来验证 id 字段中的空值。您可以通过idField注解属性在注解中定义id字段的名称,因此您不限于id。这也可以用于其他对象,因此您不必像您在第二个解决方案中建议的那样重复检查该值。

    你可以玩弄它。只是想我会把这个选项扔在那里。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.validation.Constraint;
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import javax.validation.Payload;
    
    @Constraint(validatedBy = NoId.NoIdValidator.class)
    @Target({ElementType.PARAMETER})
    @Retention(RUNTIME)
    public @interface NoId {
    
        String message() default "Cannot have value for id attribute";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        String idField() default "id";
    
        public static class NoIdValidator implements ConstraintValidator<NoId, Object> {
            private String idField;
    
            @Override
            public void initialize(NoId annotation) {
                idField = annotation.idField();
            }
    
            @Override
            public boolean isValid(Object bean, ConstraintValidatorContext cvc) {
    
                boolean isValid = false;
                try {
                    Field field = bean.getClass().getDeclaredField(idField);
                    if (field == null) {
                        isValid = true;
                    } else {
                        field.setAccessible(true);
                        Object value = field.get(bean);
                        if (value == null) {
                            isValid = true;
                        }
                    }
                } catch (NoSuchFieldException 
                        | SecurityException 
                        | IllegalArgumentException 
                        | IllegalAccessException ex) {
                    Logger.getLogger(NoId.class.getName()).log(Level.SEVERE, null, ex);
                }
                return isValid;
            }
        }
    }
    

    用法:

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createBook(@Valid @NoId(idField = "id") Book book) {
        book.setId(1);
        return Response.created(URI.create("http://blah.com/books/1"))
                .entity(book).build();
    }
    

    注意默认idFieldid,所以如果你不指定它,它会在对象类中寻找id字段。您还可以像指定任何其他约束注释一样指定message

    @NoId(idField = "bookId", message = "bookId must not be specified")
                              // default "Cannot have value for id attribute"
    

    【讨论】:

      猜你喜欢
      • 2012-07-03
      • 1970-01-01
      • 2014-10-25
      • 2013-01-27
      • 2012-10-28
      • 2014-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多