Spring + JPA事务已创建并已提交,但不会导致db

时间:2013-01-28 20:58:07

标签: java spring hibernate jpa transactions

我已经阅读了很多线程抱怨类似的问题,但在我的案例中没有一个帮助我。无论如何,问题是我写了几个类:

人员实体:

@Entity
@Table(name="person")
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(name="name")
    private String name;

    public Long getId() {
        return id;
    }

    public Person setId(Long id) {
        this.id = id;
        return this;
    }

    public String getName() {
        return name;
    }

    public Person setName(String name) {
        this.name = name;
        return this;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Person)) {
            return false;
        }
        Person other = (Person) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.myhome.event_manager.entity.Person[ id=" + id + " ]";
    }

}

的PersonDAO:

@Repository
public class PersonDAO extends AbstractDAO<Person> {


    public PersonDAO() {
        setClazz(Person.class);
    }

    public Person findByName(String name) {
        return em.createQuery("WHERE p.name = :name", Person.class)
            .setParameter("name", name)
            .getSingleResult();
    }
}

AbstractDao的:

public abstract class AbstractDAO<T extends Serializable> {

@PersistenceContext(unitName="pu-main")
protected EntityManager em;
protected Class< T> clazz;

public void setClazz(final Class< T> clazzToSet) {
    this.clazz = clazzToSet;
}

public void setEm(EntityManager em) {
    this.em = em;
}

public EntityManager getEm() {
    return em;
}

public T findById(final Long id) {
    return this.em.find(this.clazz, id);
}

public List< T> findAll() {
    return this.em.createQuery("from " + this.clazz.getName())
            .getResultList();
}

public void create(final T entity) {
    this.em.persist(entity);
}

public void update(final T entity) {
    this.em.merge(entity);
}

public void delete(final T entity) {
    this.em.remove(entity);
}

public void deleteById(final Long entityId) {
    final T entity = this.findById(entityId);
    this.delete(entity);
}

}

和简单的 PersonService ,在这种情况下是带有@Transactional方法的DAO包装器createPerson:

@Service
public class PersonService {

@Autowired
private PersonDAO personDao;

@Transactional
public void createPerson(Person person) {
    personDao.create(person);
}

public List<Person> listAllPersons() {
    return personDao.findAll();
}

public Person getPersonWithName(String name) {
    return personDao.findByName(name);
}

}

的pom.xml:

<properties>
    <java-version>1.6</java-version>
    <org.springframework-version>3.2.0.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <netbeans.hint.deploy.server>Tomcat</netbeans.hint.deploy.server>
</properties>
<dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${org.springframework-version}</version>
        <exclusions>
            <!-- Exclude Commons Logging in favor of SLF4j -->
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${org.springframework-version}</version>
    </dependency>

    <!--Mysql driver-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.21</version>
    </dependency>

    <dependency>
        <groupId>org.hsqldb</groupId>
        <artifactId>hsqldb</artifactId>
        <version>2.2.9</version>
    </dependency>

    <!--Hibernate-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.1.9.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>4.3.1.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.0-api</artifactId>
        <version>1.0.1.Final</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${org.aspectj-version}</version>
    </dependency>   

    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j-version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.15</version>
        <exclusions>
            <exclusion>
                <groupId>javax.mail</groupId>
                <artifactId>mail</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.jms</groupId>
                <artifactId>jms</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jdmk</groupId>
                <artifactId>jmxtools</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
        <scope>runtime</scope>
    </dependency>

    <!-- @Inject -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>

    <!-- Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.1</version>
        <scope>provided</scope>
    </dependency>

    <!--JSTL-->
    <dependency>
        <groupId>jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- Test -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.9.0</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.easytesting</groupId>
        <artifactId>fest-assert</artifactId>
        <version>1.4</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${org.springframework-version}</version>
        <scope>test</scope>
        <type>jar</type>
    </dependency>
</dependencies>

以上所有内容与root-context.xml中包含的配置一起使用:

DataSource.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"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!--MysQL - main-->
<bean id="dataSource" 
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="database" value="MYSQL" />
    <property name="showSql" value="true"/>
    <property name="generateDdl" value="true"/>
    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
</bean>

<bean id="emFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
    <property name="persistenceUnitName" value="pu-main" />
</bean>

Transactions.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:tx="http://www.springframework.org/schema/tx"

   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

<!--enable the configuration of transactional behavior based on annotations--> 
<tx:annotation-driven transaction-manager="txManager" />

<!--Transactions-->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

</beans>

的persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="pu-main" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source/>
    <properties/>
  </persistence-unit>
</persistence>

接下来我做了一个创建新Person实例的jUnit测试,并调用PersonService的create方法,该方法应该将它传递给DAO和持久层。

测试:

public class PersonServiceTest extends RootContextAwareTest {

@Autowired
private PersonService instance;

@Test
public void shouldAddOnePerson() {
    logger.debug("TEST-shouldAddOnePerson");

    //given
    Person person = (new Person())
            .setName("Test");

    //when
    logger.debug("BEFORE CREATE");
    instance.createPerson(person);
    logger.debug("AFTER CREATE");


    //then
    assertThat(instance.listAllPersons()).hasSize(6);
    assertThat(instance.getPersonWithName("Test"));
}

}

RootContextAwareTest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/config/spring/root-context.xml"})
public abstract class RootContextAwareTest {
     protected static final Logger logger = LoggerFactory.getLogger(RootContextAwareTest.class);
}

实际上在person表中有5行,所以我只是在持久化新实体之后放置一个断言,检查它是否正确持久化。事实证明它不是,并且断言失败但令我惊讶的是,日志说一切顺利:

    2013-01-28 21:28:07,148 - DEBUG: com.myhome.event_manager.test_base.RootContextAwareTest - TEST-shouldAddOnePerson
2013-01-28 21:28:07,148 - DEBUG: com.myhome.event_manager.test_base.RootContextAwareTest - BEFORE CREATE
2013-01-28 21:28:07,154 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.myhome.event_manager.service.PersonService.createPerson]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2013-01-28 21:28:07,155 - DEBUG: org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/event_manager]
2013-01-28 21:28:07,172 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.jdbc.JDBC4Connection@164a38ae] for JDBC transaction
2013-01-28 21:28:07,178 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@164a38ae] to manual commit
2013-01-28 21:28:07,208 - DEBUG: org.springframework.orm.jpa.EntityManagerFactoryUtils - Opening JPA EntityManager
2013-01-28 21:28:07,249 - DEBUG: org.springframework.orm.jpa.EntityManagerFactoryUtils - Registering transaction synchronization for JPA EntityManager
2013-01-28 21:28:07,267 - DEBUG: org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
2013-01-28 21:28:07,268 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
2013-01-28 21:28:07,269 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@164a38ae]
2013-01-28 21:28:07,269 - DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@164a38ae] after transaction
2013-01-28 21:28:07,269 - DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
2013-01-28 21:28:07,270 - DEBUG: com.myhome.event_manager.test_base.RootContextAwareTest - AFTER CREATE

所以我认为(在一些研究之后)测试env默认情况下不允许对db进行任何更改(这是非常合乎逻辑的)。 所以我的第二次尝试是添加注释:

@Transactional
@TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
在PersonServiceTest类上。结果就像创建,提交和发布事务之前没有任何错误一样。我再次进行了一些研究并制作了一个简单的控制器并放置了应该创建并保持新人实体的代码。

MainController:

@Controller
public class MainController {

    private static final Logger logger = LoggerFactory.getLogger(MainController.class);
    private ApplicationContext context = new ClassPathXmlApplicationContext("/config/spring/root-context.xml");
    @Autowired
    private PersonService ps;

    @RequestMapping(method = RequestMethod.GET, value = "/")
    public String index(Model model) {
        Person person = (new Person())
                .setName("Test-ąęć");

        logger.debug("PERSIST PERSON");
        ps.createPerson(person);
        return "forward:/event/read";
    }
}

结果与上述相同。

我还试过把@Transactional注释做DAO(类和方法级别)和控制器。

这种行为应该指出持久层配置的问题,但从数据库中选择数据工作正常。

此外,我发现在将数据写入db之前,hibernate可能会有一些延迟,所以我在测试中添加了thread.sleep(5000),但它并没有太大的改变。

也许我在配置上错过了一些东西,但我现在已经没想完了。

如果有任何帮助,我将不胜感激。

3 个答案:

答案 0 :(得分:2)

嗯你的问题看起来很复杂,但是我意识到你在Transactions.xml上使用了DatasourceTransactionManager,而你似乎在使用JPA持久性。

也许尝试使用JpaTransactionManager。也许DatasourceTransactionManager没有为您使用@Transactional注释的方法提供正确的事务

答案 1 :(得分:0)

在处理ORM映射时,您应该始终确保刷新您的EntityManager(如果您使用的是Hibernate会话而不是JPA),以避免false positives之前做出断言,例如

@Transactional
public class PersonServiceTest {
    @PersistenceContext
    EntityManager em;

    @Test
    public void someTest() {
        // given (initialization)

        // when (write stuff to be persisted)

        em.flush();

        // then (read and validate)
    }
}

答案 2 :(得分:0)

数据库中的Person Id列是否允许为空?如果是这样,新创建的Person可能与现有行“相等”。这会导致更新,而不是插入。