Spring中持久层事务配置

前面写了这么久的持久层框架存储数据,今天来记录下 Spring 框架对于事物管理的配置,顺带记录下 Spring 框架与 Hibernate 和 Mybatis 这些持久层框架的整合。

Spring 为事务管理提供了一致的编程模版,在高层次建立了统一的事务抽象,不管用户选择 Spring JDBC、Hibernate、JPA 还是选择 MyBatis,Spring 都可以让用户用统一的编程模型进行事务管理。这种统一处理的方式带来的好处是不可估量的,用户完全可以抛开事务管理的问题编写程序,并在 Spring 中通过配置完成事务的管理工作。

一、Spring 事务管理实现类

Spring 将事务管理委托给底层具体的持久化框架来完成。因此,Spring 为不同的持久化框架提供了不同的 PlantformTransactionManager 接口的实现类。

事务类 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或者 MyBatis 等基于 DataSource 数据源的持久化技术时,使用该事务管理器
org.springframework.orm.hibernateX.HibernateTemplate 使用 Hibernate X.0 版本进行持久化时,使用该事务管理器
org.springframework.orm.jpa.JpaTransactionManager 使用 JPA 进行持久化时候,使用该事务管理器

二、Spring JDBC 事务管理配置

Dao 层实现

public class ContactDaoImpl implements ContactDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Contact findObjectById(int id) throws Exception {
        String sql = "select * from contact where id=?";
        return jdbcTemplate.queryForObject(sql, new RowMapper<Contact>() {
            @Override
            public Contact mapRow(ResultSet rs, int rowNum) throws SQLException {
                Contact contact = new Contact();
                contact.setId(rs.getInt("id"));
                contact.setName(rs.getString("name"));
                contact.setBirthday(rs.getDate("birthday"));
                return contact;
            }
        }, id);
    }

    @Override
    public List<Contact> findAll() throws Exception {
        String sql = "select * from contact";
        return jdbcTemplate.query(sql, new RowMapper<Contact>() {
            @Override
            public Contact mapRow(ResultSet rs, int rowNum) throws SQLException {
                Contact contact = new Contact();
                contact.setId(rs.getInt("id"));
                contact.setName(rs.getString("name"));
                contact.setBirthday(rs.getDate("birthday"));
                return contact;
            }
        });
    }

    @Override
    public int insertObject(Contact contact) throws Exception {
        String sql = "insert into contact (name, birthday) values (?, ?)";
        return jdbcTemplate.update(sql, contact.getName(), contact.getBirthday());
    }

    @Override
    public int updateObject(Contact contact) throws Exception {
        String sql = "update contact set name = ?, birthday = ? where id = ?";
        return jdbcTemplate.update(sql, contact.getName(), contact.getBirthday(), contact.getId());
    }

    @Override
    public int deleteObjectById(int id) throws Exception {
        String sql = "delete from contact where id=?";
        return jdbcTemplate.update(sql, id);
    }
}

Service 层实现

public class ContactServiceImpl implements ContactService {

    private ContactDao contactDao;

    public void setContactDao(ContactDao contactDao) {
        this.contactDao = contactDao;
    }

    @Override
    public Contact findObjectById(int id) throws Exception {
        return contactDao.findObjectById(id);
    }

    @Override
    public List<Contact> findAll() throws Exception {
        return contactDao.findAll();
    }

    @Override
    public int insertObject(Contact contact) throws Exception {
        int count = contactDao.insertObject(contact);
        return count;
    }

    @Override
    public int updateObject(Contact contact) throws Exception {
        return contactDao.updateObject(contact);
    }

    @Override
    public int deleteObjectById(int id) throws Exception {
        return contactDao.deleteObjectById(id);
    }
}
<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driver}"></property>
        <property name="jdbcUrl" value="${url}"></property>
        <property name="user" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="initialPoolSize" value="${initialPoolSize}"></property>
        <property name="minPoolSize" value="${minPoolSize}"></property>
        <property name="maxPoolSize" value="${maxPoolSize}"></property>
        <property name="maxIdleTime" value="${maxIdleTime}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>
    </bean>

    <bean id="contactDao" class="com.ogemray.jdbc.dao.impl.ContactDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <bean id="contactService" class="com.ogemray.jdbc.service.impl.ContactServiceImpl">
        <property name="contactDao" ref="contactDao"></property>
    </bean>

    <!--开始事务配置-->

    <!--事务管理类-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--事务增强配置-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" read-only="false"/>
        </tx:attributes>
    </tx:advice>

    <!--AOP配置-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.ogemray.jdbc.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>

注意: 只要将 jdbcTemplate 注入到 Dao 的实现层就能实现接下来的 JDBC 操作,同时需要注意的是,事务管理配置在 Service 层,所以不管是 Dao 层还是 Service 层,都要将异常往上抛,这样才能达到事务管理效果,如果在 Service 层或者下层进行错误抛出,则到不到事务管理效果。

三、Spring 与 MyBatis 整合 事务管理配置

Dao 层接口

public interface ContactMapper {

    public List<Contact> findAll() throws Exception;

    public Contact findObjectById(int id) throws Exception;

    public int insertObject(Contact contact) throws Exception;

    public int updateObject(@Param("contact") Contact contact) throws Exception;

    public int deleteObjectById(int id) throws Exception;
}

Mapper XML 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ogemray.mybatis.mapper.ContactMapper">

    <resultMap id="contactMap" type="com.ogemray.mybatis.entity.Contact">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="birthday" column="birthday"></result>
    </resultMap>

    <select id="findAll" resultMap="contactMap">
        select * from contact
    </select>

    <select id="findObjectById" resultMap="contactMap">
        select  * from  contact where id = #{id}
    </select>

    <insert id="insertObject"
            parameterType="com.ogemray.mybatis.entity.Contact"
            useGeneratedKeys="true"
            keyProperty="id">
        insert into contact (name, birthday) values (#{name}, #{birthday})
    </insert>

    <update id="updateObject">
        update contact
        <set>
            <trim suffixOverrides=",">
                <if test="contact.name != null">name = #{contact.name}, </if>
                <if test="contact.birthday != null">birthday = #{contact.birthday}</if>
            </trim>
        </set>
        where id = #{contact.id}
    </update>

    <delete id="deleteObjectById">
        delete from contact where id = #{id}
    </delete>
</mapper>

Service 层实现

public class ContactServiceImpl implements ContactService {

    private ContactMapper contactMapper;

    public void setContactMapper(ContactMapper contactMapper) {
        this.contactMapper = contactMapper;
    }

    @Override
    public List<Contact> findAll() throws Exception {
        return contactMapper.findAll();
    }

    @Override
    public Contact findObjectById(int id) throws Exception {
        return contactMapper.findObjectById(id);
    }

    @Override
    public int insertObject(Contact contact) throws Exception {
        int count = contactMapper.insertObject(contact);
        return count;
    }

    @Override
    public int updateObject(Contact contact) throws Exception {
        return contactMapper.updateObject(contact);
    }

    @Override
    public int deleteObjectById(int id) throws Exception {
        return contactMapper.deleteObjectById(id);
    }
}
<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
	   http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driver}"></property>
        <property name="jdbcUrl" value="${url}"></property>
        <property name="user" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="initialPoolSize" value="${initialPoolSize}"></property>
        <property name="minPoolSize" value="${minPoolSize}"></property>
        <property name="maxPoolSize" value="${maxPoolSize}"></property>
        <property name="maxIdleTime" value="${maxIdleTime}"></property>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations" value="classpath*:com/ogemray/mybatis/mapper/*.xml"></property>
        <property name="configLocation" value="classpath:mybaitis-config.xml"></property>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ogemray.mybatis.mapper"></property>
    </bean>

    <bean id="contactService" class="com.ogemray.mybatis.service.ContactServiceImpl">
        <property name="contactMapper" ref="contactMapper"></property>
    </bean>

    <!--事务配置注入-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--事务增强配置-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*" read-only="false" no-rollback-for="java.lang.ArithmeticException"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务及切点-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.ogemray.mybatis.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
    </aop:config>

</beans>

四、Spring 与 Hibernate 整合 事务管理配置

Dao 层实现类

public class ContactDaoImpl implements ContactDao {

    private SessionFactory sessionFactory;
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public List<Contact> findAll() throws Exception {
        String sql = "from " + Contact.class.getSimpleName();
        List<Contact> list = getSession().createQuery(sql).getResultList();
        return list;
    }

    @Override
    public Contact findById(int id) throws Exception {
        return getSession().get(Contact.class, id);
    }

    @Override
    public Serializable insertObject(Contact contact) throws Exception {
        return getSession().save(contact);
    }

    @Override
    public void updateObject(Contact contact) throws Exception {
        getSession().update(contact);
    }

    @Override
    public void deleteObject(Contact contact) throws Exception {
        getSession().delete(contact);
    }
}

Service 层实现

public class ContactServiceImpl implements ContactService {
    private ContactDao contactDao;

    public void setContactDao(ContactDao contactDao) {
        this.contactDao = contactDao;
    }

    public List<Contact> findAll() throws Exception {
        return contactDao.findAll();
    }

    public Contact findById(int id) throws Exception {
        return contactDao.findById(id);
    }

    public Serializable insertObject(Contact contact) throws Exception {
        Serializable serializable = contactDao.insertObject(contact);
        return serializable;
    }

    public void updateObject(Contact contact) throws Exception {
        contactDao.updateObject(contact);
    }

    public void deleteObject(Contact contact) throws Exception {
        contactDao.deleteObject(contact);
    }
}
<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driver}"></property>
        <property name="jdbcUrl" value="${url}"></property>
        <property name="user" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="initialPoolSize" value="${initialPoolSize}"></property>
        <property name="minPoolSize" value="${minPoolSize}"></property>
        <property name="maxPoolSize" value="${maxPoolSize}"></property>
        <property name="maxIdleTime" value="${maxIdleTime}"></property>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <!--<property name="configLocation" value="hibernate.cfg.xml"></property>-->
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL55Dialect</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">false</prop>
            </props>
        </property>

        <property name="mappingLocations">
            <list>
                <value>classpath:com/ogemray/hibernate/entity/*.hbm.xml</value>
            </list>
        </property>
    </bean>

    <bean id="contactDao" class="com.ogemray.hibernate.dao.impl.ContactDaoImpl">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>

    <bean id="contactService" class="com.ogemray.hibernate.service.ContactServiceImpl">
        <property name="contactDao" ref="contactDao"></property>
    </bean>

    <!--事务配置-->
    <bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" read-only="false"></tx:method>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.ogemray.hibernate.service.*.*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>

另外也可以在 Spring 中注入 HibernateTempate 来完成持久层操作

<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean>

<bean id="contactDao" class="com.ogemray.hibernate.dao.impl.ContactDaoImpl">
    <property name="hibernateTemplate" ref="hibernateTemplate"></property>
</bean>

持久层代码修改

public class ContactDaoImpl implements ContactDao {
    private HibernateTemplate hibernateTemplate;
    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    @Override
    public List<Contact> findAll() throws Exception {
        String sql = "from " + Contact.class.getSimpleName();
        List<Contact> list = (List<Contact>) hibernateTemplate.find(sql);
        return list;
    }

    @Override
    public Contact findById(int id) throws Exception {
        return hibernateTemplate.get(Contact.class, id);
    }

    @Override
    public void updateObject(Contact contact) throws Exception {
        hibernateTemplate.update(contact);
    }
}