Mar*_*tin 5 java jpa jax-rs java-ee
我有一个非常基本的JAX-RS服务(BookService下面的类),它允许创建类型的实体Book(也在下面).POST有效载荷
{
"acquisitionDate": 1418849700000,
"name": "Funny Title",
"numberOfPages": 100
}
Run Code Online (Sandbox Code Playgroud)
成功地坚持Book并返回201 CREATED.但是,id在有效负载上包含具有任何非空值的属性会触发org.hibernate.PersistentObjectException消息detached entity passed to persist.我理解这意味着什么,并且id在创建对象时包含有效负载(在这种情况下)是没有意义的.但是,我宁愿防止这个异常一直向上冒泡,并400 BAD REQUEST在这种情况下向我的用户呈现(或者,至少完全忽略该属性).但是,有两个主要问题:
create是一个EJBTransactionRolledbackException,我必须一直爬到堆栈跟踪中以发现根本原因;org.hibernate.PersistentObjectException- 我正在部署到使用Hibernate的Wildfly,但我想保持我的代码可移植,所以我真的不想捕获这个特定的异常.据我了解,有两种可能的解决方案:
book.setId(null)之前使用bookRepo.create(book).这将忽略id属性携带值并继续执行请求的事实.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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是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/...)
}
Run Code Online (Sandbox Code Playgroud)
这是BookRepo班级:
@Stateless
public class BookRepo {
@PersistenceContext(unitName = "book-repo")
protected EntityManager em;
public void create(Book book) {
em.persist(book);
}
}
Run Code Online (Sandbox Code Playgroud)
我不知道这是否真的是您正在寻找的答案,但我只是在尝试这个想法并实现了一些东西。
JAX-RS 2 规范定义了 bean 验证的模型,所以我想也许您可以利用它。所有错误的验证都会映射到 400。您说过“我希望防止此异常一直冒泡,并向我的用户提供例如 400 BAD REQUEST”,但是如果验证错误,您将得到该结果反正。因此,无论您计划如何处理验证异常(如果有的话),您都可以在此处执行相同的操作。
基本上我只是创建了一个约束注释来验证 id 字段中的空值。您可以通过annotation属性在注释中定义id字段的名称idField,这样您就不受限于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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
@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();
}
Run Code Online (Sandbox Code Playgroud)
请注意,默认值为idField,因此如果您不指定它,它将在对象类中id查找该字段。id您还可以像message指定任何其他约束注释一样指定:
@NoId(idField = "bookId", message = "bookId must not be specified")
// default "Cannot have value for id attribute"
Run Code Online (Sandbox Code Playgroud)