Dans mon projet, j'utilise Hibernate avec la démarcation programmatique de transactions. Chaque fois que dans mes méthodes de service que j'écrire quelque chose de semblable à cela.
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
.. perform operation here
session.getTransaction().commit();
Maintenant, je vais refactoriser mon code avec la gestion déclarative des transactions. Ce que j'ai maintenant ...
<context:component-scan base-package="package.*"/>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean>
Classe de service:
@Service
public class TransactionalService {
@Autowired
private SessionFactory factory;
@Transactional
public User performSimpleOperation() {
return (User)factory.getCurrentSession().load(User.class, 1L);
}
}
Et test simple -
@Test
public void useDeclarativeDem() {
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("spring-config.xml");
TransactionalService b = (TransactionalService)ctx.getBean("transactionalService");
User op = b.performSimpleOperation();
op.getEmail();
Lorsque j'essaie d'obtenir un e-mail d'utilisateur en dehors de la méthode Transactionnelle, j'ai une exception d'initialisation différée, l'email est mon cas est une simple chaîne. Hibernate n'effectue même pas de requête SQL, jusqu'à ce que j'appelle des accesseurs sur mon POJO.
1) Qu'est-ce que je fais mal ici?
2) Cette approche est-elle valide?
3) Pouvez-vous suggérer des projets opensources qui fonctionnent sur spring/hibernate avec une configuration basée sur des annotations?
Mettre à jour
Pour une raison quelconque, si je remplace getCurrentSession avec openSession ce code fonctionne correctement. Quelqu'un peut-il expliquer s'il vous plaît?
Je vous remercie
Ok, finalement j'ai compris quel était le problème. Dans le code ci-dessus, j'ai utilisé load au lieu de get. Session.load n'a pas réellement touché la base de données. C'est la raison pour laquelle je reçois une exception d'initialisation paresseuse en dehors de la méthode @Transactional
Si j'utilise openSession au lieu de getCurrentSession, la session est ouverte en dehors du conteneur spring scope. En tant que résultat, la session n'était pas proche et me permettait de lire les propriétés d'un objet en dehors de la méthode @Transactional
La raison pour laquelle Hibernate n'effectue aucune requête SQL jusqu'à ce que vous appeliez les accesseurs, c'est parce que je crois que FetchType est défini sur LAZY. Pour résoudre ce problème, vous devrez changer le FetchType en EAGER dans votre POJO:
@Entity
@Table(name = "user")
public class User {
/*Other data members*/
@Basic(fetch = FetchType.EAGER)
private String email;
}
Personnellement, je n'ai jamais eu à spécifier de types Basic pour avoir un EAGER FetchType, je ne suis donc pas tout à fait sûr de savoir pourquoi votre configuration l'exige. Si ce n'est que dans vos tests, cela pourrait être dû à la configuration de vos tests JUnit. Il devrait y avoir quelque chose comme ceci sur la déclaration de classe:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/test-app-config.xml"})
@Transactional
public class UserServiceTest {
}
En ce qui concerne une bonne ressource, je trouve toujours que SpringByExample est utile.
EDIT
Donc, je ne suis pas tout à fait sûr de ce qui ne va pas avec votre configuration. Cela diffère de la façon dont j'ai configuré la mienne, voici donc ma configuration typique dans l'espoir que cela aide. Le hibernate.transaction.factory_class
pourrait être une propriété de clé qui vous manque. J'utilise aussi la AnnotationSessionFactoryBean
:
<!-- DataSource -->
<bean id="dataSource" class="org.Apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost/dbname"
p:username="root"
p:password="password"/>
<!-- Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource">
<property name="packagesToScan" value="com.beans"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</prop>
</props>
</property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
D'après la documentation d'Hibernate https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch11.html#objectstate-loading :
Sachez que load () lève une exception irrécupérable s'il n'y a pas de ligne correspondante dans la base de données. Si la classe est mappée avec un proxy, load () renvoie simplement un proxy non initialisé et ne frappe pas la base de données tant que vous n'avez pas appelé une méthode du proxy. Ceci est utile si vous souhaitez créer une association à un objet sans le charger réellement à partir de la base de données. Il permet également de charger plusieurs instances en tant que lot si la taille du lot est définie pour le mappage de classe.
À partir de la documentation ci-dessus, le bean de votre cas est mappé avec un proxy printanier. Par conséquent, vous devez utiliser get () au lieu de load (), qui frappe toujours la base de données.