Java 防止';PersistentObjectException';
我有一个非常基本的JAX-RS服务(下面的Java 防止';PersistentObjectException';,java,jakarta-ee,jpa,jax-rs,Java,Jakarta Ee,Jpa,Jax Rs,我有一个非常基本的JAX-RS服务(下面的BookService类),它允许创建Book类型的实体(也在下面)POST装载有效负载 { "acquisitionDate": 1418849700000, "name": "Funny Title", "numberOfPages": 100 } 成功保存书籍,并返回创建的201。但是,在有效负载上包含带有非空值的id属性会触发org.hibernate.PersistentObjectException,并将消息分离实体
BookService
类),它允许创建Book
类型的实体(也在下面)<代码>POST装载有效负载
{
"acquisitionDate": 1418849700000,
"name": "Funny Title",
"numberOfPages": 100
}
成功保存书籍
,并返回创建的201
。但是,在有效负载上包含带有非空值的id
属性会触发org.hibernate.PersistentObjectException
,并将消息分离实体传递给persist
我理解这意味着什么,在创建对象(在本例中)时,在有效负载上包含一个id
是没有意义的。但是,我更愿意防止这个异常一直冒泡,并向我的用户提供一个400错误请求
(或者至少完全忽略该属性)。然而,有两个主要问题:
create
的异常是一个EJBTransactionRolledbackException
,我必须沿着堆栈跟踪一直爬行以发现根本原因李>
org.hibernate.PersistentObjectException
——我正在部署使用hibernate的Wildfly,但我想保持代码的可移植性,所以我不想捕获这个特定的异常bookRepo.create(book)
之前使用book.setId(null)
。这将忽略id
属性携带一个值并继续请求这一事实book.getId()!=null
并抛出类似于IllegalArgumentException
的内容,该内容可以映射到400
状态代码。似乎是更好的解决办法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();
}
}
@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/...)
}
@Stateless
public class BookRepo {
@PersistenceContext(unitName = "book-repo")
protected EntityManager em;
public void create(Book book) {
em.persist(book);
}
}
这是书籍
课程:
@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();
}
}
@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/...)
}
@Stateless
public class BookRepo {
@PersistenceContext(unitName = "book-repo")
protected EntityManager em;
public void create(Book book) {
em.persist(book);
}
}
这是BookRepo
类:
@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();
}
}
@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/...)
}
@Stateless
public class BookRepo {
@PersistenceContext(unitName = "book-repo")
protected EntityManager em;
public void create(Book book) {
em.persist(book);
}
}
我不知道这是否真的是你想要的答案,但我只是在玩弄这个想法并实现了一些东西 JAX-RS2规范定义了一个用于bean验证的模型,所以我想您可以利用它。所有错误的验证都将映射到400。您声明“我更愿意防止这个异常冒泡,并向我的用户提供一个400个错误的请求”,但如果验证不正确,您将得到它。因此,无论您计划如何处理验证异常(如果有),您都可以在这里执行相同的操作 基本上,我只是创建了一个约束注释来验证id字段中的空值。您可以通过
idField
annotation属性在注释中定义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;
}
}
}
请注意,默认的idField
是id
,因此如果不指定它,它将在对象类中查找id
字段。您还可以像指定任何其他约束注释一样指定消息
:
@NoId(idField = "bookId", message = "bookId must not be specified")
// default "Cannot have value for id attribute"