What's wrong in this Hibernate/Spring application (standalone)?
I have searched Google for the standalone Hibernate/Spring applications, but didn't find any useful sample. It seems like most people use it for web apps only.
Here is what I have:
The main class:
@Component
public class App {
@Inject
SessionFactory sessionFactory;
Fruit apple;
Serializable appleId;
@Transactional
void testCreate() {
apple = new Fruit();
apple.setName("Apple");
apple.setPrice(10);
HibernateTemplate template = new HibernateTemplate(sessionFactory);
appleId = template.save(apple);
System.out.println("New Apple: " + apple);
}
@Transactional
void testReload() {
HibernateTemplate template = new HibernateTemplate(s开发者_开发技巧essionFactory);
final Fruit reload = template.load(Fruit.class, appleId);
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
System.out.println("Update");
session.update(reload);
System.out.println("Reload: " + reload);
}
public void run()
throws Exception {
testCreate();
testReload();
}
public static void main(String[] args)
throws Exception {
new ClassPathXmlApplicationContext("context.xml").getBean(App.class).run();
}
}
In this example, after successfully inserted a new Apple
to the database, the subsequence reload() function thrown:
The output:
Hibernate:
/* insert my.hibernate.Fruit
*/ insert
into
Food
(id, rank, version, name, price, DTYPE)
values
(null, ?, ?, ?, ?, 'Fruit')
DEBUG [main] (HibernateAccessor.java:389) - Eagerly flushing Hibernate session
New Apple: 1, Apple
DEBUG [main] (HibernateAccessor.java:389) - Eagerly flushing Hibernate session
Update
Hibernate:
/* load my.hibernate.Fruit */ select
fruit0_.id as id0_0_,
fruit0_.rank as rank0_0_,
fruit0_.version as version0_0_,
fruit0_.name as name0_0_,
fruit0_.price as price0_0_
from
Food fruit0_
where
fruit0_.id=?
and fruit0_.DTYPE='Fruit'
Exception in thread "main" org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [my.hibernate.Fruit#1]
at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:419)
at org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:154)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:143)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:174)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at my.hibernate.Fruit_$$_javassist_0.toString(Fruit_$$_javassist_0.java)
at java.lang.String.valueOf(String.java:2902)
at java.lang.StringBuilder.append(StringBuilder.java:128)
at my.hibernate.App.testReload(App.java:86)
It seems like testCreate()
didn't commit anything. Any idea?
EDIT
The context.xml
:
<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<context:component-scan base-package="my" />
</beans>
And the session-factory config:
@Component
public class TestH2DataSource
extends BasicDataSource {
public TestH2DataSource() {
setDriverClassName("org.h2.Driver");
setUrl("jdbc:h2:target/testdb;DB_CLOSE_ON_EXIT=FALSE");
setUsername("sa");
setPassword("");
setDefaultAutoCommit(false);
}
}
@Component
public class TestSessionFactory
extends AnnotationSessionFactoryBean {
@Inject
DataSource dataSource;
public TestSessionFactory() {
Properties properties = new Properties();
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "true");
properties.setProperty("hibernate.use_sql_comments", "true");
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
this.setHibernateProperties(properties);
this.setAnnotatedClasses(new Class<?>[] { Fruit.class });
}
@Override
public void afterPropertiesSet()
throws Exception {
this.setDataSource(dataSource);
super.afterPropertiesSet();
}
}
@Configuration
public class OtherContextConfiguration {
@Inject
SessionFactory sessionFactory;
@Bean
public HibernateInterceptor hibernateInterceptor() {
HibernateInterceptor hibernateInterceptor = new HibernateInterceptor();
hibernateInterceptor.setSessionFactory(sessionFactory);
return hibernateInterceptor;
}
@Bean
public HibernateTransactionManager hibernateTransactionManager() {
HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager();
hibernateTransactionManager.setSessionFactory(sessionFactory);
return hibernateTransactionManager;
}
}
No commit takes place in the database. How do you invoke the function testCreate(). I suppose you are using spring-aop for @Transactional. Spring AOP can intercept @Transactional only on proxy objects and not the actual instance of the class. So if your App class is not a spring proxy then it wont be able to commit to database. Futhermore internal calls to a private method inside a class also wont trigger the @Transactional. So, you should inject the App class into you service layer class, which will then invoke the testCreate() method on the injected instance of the App.class. Since the injected instance of the App class will be a proxy so spring will take care of the transaction.
That shouldn't be a problem actually, you can safely annotate a method with @Transactional and spring container can take care of the transaction, only you should be careful that you call the transactional methods on proxy instances of the class containing the methods and not the actual instance of the class when using spring-aop, as spring can only intercept proxy instances of the classes for managing transactions. The simplest way of ensuring that is always using the injected bean instance of the class. Also, make sure that you do the proper configuration for your app, defining the proper beans for transaction management. The essence of spring is Dependency Injection and you can only harness the full power of the spring container if you use the dependency injection fully. Stay away from using the 'new' keyword anywhere in the code unless you are sure of what you are doing. The class which contains the transactional methods should be injected in your service or web layer depending on the nature of your application. If you want to call transactional methods on the actual instance of the class i.e one created using 'new', then you should use AspectJ for AOP. The spring documentation explains how to configure AspectJ for your spring application.
精彩评论