防止'PersistentObjectException' [英] Preventing 'PersistentObjectException'

查看:110
本文介绍了防止'PersistentObjectException'的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a very basic JAX-RS service (the BookService class below) which allows for the creation of entities of type Book (also below). POSTing the payload

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

成功保留 Book 并返回 201 CREATED 。但是,包含 id 属性以及有效负载上的任何非空值都会触发 org.hibernate.PersistentObjectException 消息传递给persist的分离实体我理解这意味着什么,并且在创建对象时(在这种情况下)有效负载上包含 id 是没有意义的。但是,在这种情况下,我宁愿防止此异常一直冒泡并向我的用户显示 400 BAD REQUEST (或者,至少,完全忽略该属性)。但是,有两个主要问题:

successfully persists the Book and returns 201 CREATED. However, including an id attribute with whichever non-null value on the payload triggers an org.hibernate.PersistentObjectException with the message detached entity passed to persist. I understand what this means, and including an id on the payload when creating an object (in this case) makes no sense. However, I'd prefer to prevent this exception from bubbling all the way up and present my users with, for instance, a 400 BAD REQUEST in this case (or, at least, ignore the attribute altogether). However, there are two main concerns:


  1. 到达创建的异常是一个 EJBTransactionRolledbackException ,我必须一直爬下堆栈跟踪以发现根本原因;

  2. 根本原因是 org.hibernate.PersistentObjectException - 我正在部署到使用Hibernate的Wildfly,但我想保持我的代码可移植,所以我真的不想抓住这个特定的异常。

  1. The exception that arrives at create is an EJBTransactionRolledbackException and I'd have to crawl all the way down the stack trace to discover the root cause;
  2. The root cause is org.hibernate.PersistentObjectException - I'm deploying to Wildfly which uses Hibernate, but I want to maintain my code portable, so I don't really want to catch this specific exception.

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

To my understanding, there are two possible solutions:


  1. bookRepo.create(book)之前使用 book.setId(null)。这将忽略 id 属性带有值并继续请求的事实。

  2. 检查是否 book.getId()!= null 并抛出类似 IllegalArgumentException 的内容,可以映射到 400 状态代码。似乎是更好的解决方案。

  1. Use book.setId(null) before bookRepo.create(book). This would ignore the fact that the id attribute carries a value and proceed with the request.
  2. Check if book.getId() != null and throw something like IllegalArgumentException that could be mapped to a 400 status code. Seems the preferable solution.

然而,来自其他框架(例如Django Rest Framework)我真的更喜欢这个由框架本身处理...我的问题是,有没有内置的方法来实现我可能会缺少的这种行为?

However, coming from other frameworks (like Django Rest Framework, for example) I'd really prefer this to be handled by the framework itself... My question then is, is there any built-in way to achieve this behaviour that I may be missing?

这是 BookService class:

@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 class:

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


推荐答案

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

I don't know if this is really the answer you're looking for, but I was just playing around with the idea and implemented something.

JAX-RS 2规范定义了bean验证的模型,所以我想也许你可以利用它。所有错误的验证都将映射到400.您说我更愿意阻止此异常一直冒泡并向我的用户显示,例如,400 BAD REQUEST,但验证不好,无论如何都会得到。所以你计划处理验证异常(如果有的话),你可以在这里做同样的事情。

The JAX-RS 2 spec defines a model for bean validation, so I thought maybe you could tap into that. All bad validations will get mapped to a 400. You stated "I'd prefer to prevent this exception from bubbling all the way up and present my users with, for instance, a 400 BAD REQUEST", but with bad validation you will get that anyway. So however you plan to handle validation exceptions (if at all), you can do the same here.

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

Basically I just created a constraint annotation to validate for a null value in the id field. You can define the id field's name in the annotation through the idField annotation attribute, so you are not restricted to id. Also this can be used for other objects too, so you don't have to repeatedly check the value, as you suggested in your second solution.

你可以玩它。只是以为我会把这个选项扔出去。

You can play around with it. Just thought I'd throw this option out there.

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

注意默认 idField id ,所以如果你没有指定它,它将在对象类中查找 id 字段。您也可以像任何其他约束注释一样指定消息

Note the default idField is id, so if you don't specify it, it will look for the id field in the object class. You can also specify the message as you would any other constraint annotation:

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

这篇关于防止'PersistentObjectException'的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆