java - 防止 'PersistentObjectException'

标签 java jakarta-ee jpa jax-rs

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

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

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

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

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

  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);
    }
}

最佳答案

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

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字段类(class)。您还可以像指定任何其他约束注释一样指定 message:

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

关于java - 防止 'PersistentObjectException',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27582083/

相关文章:

java - Tomcat 服务器上的 SSL 证书

java - 无法找到或加载 oracle.jdbc.driver.OracleDriver

java - 布局小不被识别/不工作

java - dwr cometd 接近

iphone - Iphone/iPad 上的 Java - Java 模拟器/代理?

java - 使用 JPA/JTA/JBOSS/CDI 不会持久化对象

Java 调试器查找实例化对象的位置

java - 我可以使用 JSP 但不能使用 JavaBean 和 Java EE 吗?

java - 即使 fetchtype 是惰性的,JPA 也会加载太多数据

jpa - 了解 FetchType.Lazy,强制 JPQL 查询 (OpenJPA)