开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜