2014-12-20 3 views
5

У меня есть очень простой сервис JAX-RS (класс BookService), который позволяет создавать объекты типа Book (также ниже). POST ИНГ полезной нагрузкиПредотвращение «PersistentObjectException»

{ 
    "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 - Я развертываю в Wildfly, который использует Hibernate, но я хочу сохранить свой код переносимым, поэтому я действительно не хочу поймать это конкретное исключение.

В моем понимании, есть два возможных решения:

  1. Использование book.setId(null) перед bookRepo.create(book). Это игнорирует тот факт, что атрибут 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); 
    } 
} 

ответ

2

мне делать, если это не знаю на самом деле это ответ, который вы ищете, но я просто играл с id и реализовали что-то.

Спецификация JAX-RS 2 определяет модель проверки боба, поэтому я подумал, что вы могли бы воспользоваться этим. Все плохие проверки будут сопоставлены с 400. Вы указали «Я бы предпочел не допустить, чтобы это исключение стало пузыриться полностью и представило моих пользователей, например, 400 BAD REQUEST», но с плохой проверкой вы будете Получите это в любом случае. Таким образом, однако вы планируете обрабатывать исключения проверки (если вообще), вы можете сделать то же самое здесь.

В основном я создал аннотацию ограничения для проверки нулевого значения в поле id. Вы можете определить имя поля 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; 
     } 
    } 
} 

Использование:

@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 поле в классе объекта. Вы также можете указать message, как и любые другие аннотации ограничений:

@NoId(idField = "bookId", message = "bookId must not be specified") 
          // default "Cannot have value for id attribute" 
Смежные вопросы