2010-06-16 15 views
6

Si tengo una relación con @OneToMany @Cascade (CascadeType.SAVE_UPDATE) de la siguiente manera¿Cómo habilitar Hibernate Interceptor cuando tengo mi Hibernate Transaction administrada por Spring?

public class One { 

    private Integer id; 

    private List<Many> manyList = new ArrayList<Many>(); 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return this.id; 
    } 

    @OneToMany 
    @JoinColumn(name="ONE_ID", updateable=false, nullable=false) 
    @Cascade(CascadeType.SAVE_UPDATE) 
    public List<Many> getManyList() { 
     return this.manyList; 
    }   

} 

Y Muchos clase

public class Many { 

    private Integer id; 

    /** 
     * required no-arg constructor 
     */ 
    public Many() {} 

    public Many(Integer uniqueId) { 
     this.id = uniqueId 
    } 

    /** 
     * Without @GeneratedValue annotation 
     * Hibernate will use assigned Strategy 
     */ 
    @Id 
    public Integer getId() { 
     return this.id; 
    } 

} 

si tengo el siguiente escenario

One one = new One(); 

/** 
    * generateUniqueId method will Take care of assigning unique id for each Many instance 
    */ 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 

Y llamo

sessionFactory.getCurrentSession().save(one); 

Antes de pasar

Según Transitive persistence documentación de referencia de Hibernate, se puede ver

Si un padre se pasa a save(), actualizar() o saveOrUpdate(), todos los niños se pasan a saveOrUpdate()

ok. Ahora vamos a ver lo que Java Persistence con Hibernate libro habla de método saveOrUpdate

Hibernate consulta la tabla de muchos para el identificador dado, y si se encuentra, Hibernate actualiza la fila. Si no se encuentra, , se requiere la inserción de una nueva fila.

que se puede traducir de acuerdo con

INSERT INTO ONE (ID) VALUES (?) 

/** 
    * I have four Many instances added To One instance 
    * So four select-before-saving 
    * 
    * I DO NOT NEED select-before-saving 
    * Because i know i have a Fresh Transient instance 
    */ 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 

INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 

Cualquier solución para evitar select-before-ahorro ??? Sí, puede

  • Añadir una columna @Version (No aplica)
  • implementar el método isTransient proporcionado por Hibernate interceptor (La opción tengo)

Así como una forma de evitar el comportamiento predeterminado al utilizar este tipo de conexión en cascada, he mejorado mi código asignando un Hibernate Interceptor a una sesión de Hibernate cuya transacción es gestionada por Spring.

Aquí va mi repositorio

Antes (Sin ningún interceptor de Hibernate): Funciona bien!

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.getCurrentSession().save(instance); 
    } 

} 

Después (con Hibernate Inteceptor): algo va mal (No se consulta SQL se realiza - Ni INSERTAR Tampoco select-before-AHORRO)

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.openSession(new EmptyInterceptor() { 
      /** 
       * To avoid select-before-saving 
       */ 
      @Override 
      public Boolean isTransient(Object o) { 
       return true; 
      } 
     }).save(instance); 
    } 

} 

Mi pregunta es: ¿Por qué la primavera no lo hace persistir en mi Entidad y sus relaciones al usar Hibernate Interceptor y ¿qué debo hacer como solución para que funcione bien?

Respuesta

3

primavera mantiene una asociación entre la sesión actual y la transacción actual (ver SessionFactoryUtils.java.) Dado que ya existe una sesión asociada a la corriente llamada al método DAO, usted tiene que utilizar esta Sesión, o dar el paso de involucrarse con los turbias detalles de asociar la nueva sesión con el contexto de transacción anterior. Probablemente sea posible, pero con un riesgo considerable, y definitivamente no es recomendable. En hibernación, si ya tiene una sesión abierta, entonces debería usarse.

Habiendo dicho eso, puede obtener la primavera para crear una nueva sesión para usted y asociarla con el contexto de transacción actual. Use SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). Si usa esto en vez de hibernate's sessionFactory, esto debería mantener la asociación con la transacción.

Inicialmente, puede codificar esto directamente en el DAO. Cuando se pruebe y se pruebe que funciona, puede tomar medidas para sacar el código de primavera de su DAO, como por ejemplo usar AOP para agregar consejos sobre los métodos add() que crean y limpian una nueva sesión.

Otra alternativa es utilizar un Interceptor global. Aunque es global, puedes darle un comportamiento localmente controlable. El TransientInterceptor contiene un threadLocal<Boolean>. Esta es la bandera del hilo actual para indicar si el interceptor debe devolver verdadero para isTransient. Lo configura en verdadero al comienzo del método add() y lo borra al final. P.ej.

class TransientInterceptor extends EntityInterceptor { 
     ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)(); 
     public boolean isTransient() { 
     return transientFlag.get()==Boolean.TRUE; 
     } 
     static public setTransient(boolean b) { 
      transientFlag.set(b); 
     } 
    } 

Y luego, en su DAO:

@Override 
public void add(SomeEntity instance) { 
    try { 
     TransientInterceptor.set(true); 
     sessionFactory.getCurrentSession().save(instance); 
    } 
    finally { 
     TransientInterceptor.set(false); 
    } 
} 

A continuación, puede configuración TransientInterceptor como un interceptor global sobre la SessionFactory (. Ej LocalSessionFactoryBean) Para que esto sea menos invasivo, se puede crear un AOP alrededor consejos para aplicar este comportamiento a todos los métodos de agregar DAO, según corresponda.

0

En el método 'después' crea una nueva sesión y no la vacía, por lo tanto, no se envía ninguna actualización a la base de datos. Esto no tiene nada que ver con Spring, pero es un comportamiento Hibernate puro.

Lo que probablemente desee es agregar un interceptor (entidad) a sessionFactory, probablemente configurado con Spring. Luego puede simplemente mantener el método add() de su repositorio como antes. Ver http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

+0

Como dije: * Puede agregar un Interceptor a la SessionFactory *. Sucede si agrego un Interceptor a SessionFactory, obtendré un comportamiento ** global **. Solo quiero agregar un Interceptor a la instancia de la Sesión que se usa con el método add (SomeEntity instance). ¿Alguna solución? –

Cuestiones relacionadas