王小码

导航

Spring(10)深入Spring 数据库事务管理(二)

一、Spring事务配置

1.编程式事务

  编程时事务以代码方式管理事务,就是说事务由开发者通过代码方式实现,这里需要使用一个事务定类接口TransactionDefinition,我们使用默认实现类DefaultTransactionDefinition就可以。这里不做重点介绍。

2.声明式事务

  编程式事务是一种约定型的事务,在大部分情况下用数据库事务时,大部分的场景是在代码中发生了异常时,需要回滚事务,而不发生异常时则是提交事务,从而保证数据库数据的一致性。从这点出发,Spring 给了一个约定 (AOP 开发也给了我们一个约定),

如果使用的是声明式事务,当业务方法不发生异常(或者发生异常 但该异常也被配置信息允许提交事务)时, Spring 就会让事务管理器提交事务。而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。

3.基于XML的声明式事务控制(配置方式)重点

(1)环境搭建

  1》引入依赖包

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xhbjava</groupId>
    <artifactId>Spring02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>


        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
</project>

  2》创建Spring配置文件并引入约束

  需要导入 aop 和 tx 两个名称空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/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">
</beans>

  3》准备实体类和数据库表

    这里我们还是以以前创建好的account表和类进行。

  4》业务接口和实现层编写

package com.xhbjava.service;

import com.xhbjava.pojo.Account;

/**
 * 账户的业务层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccountService {
/**
 * 根据id查询账户信息
 * @param id
 * @return
 */
Account findAccountById(Integer id);

    /**
     * 转账
     * 
     * @param sourceName
     * @param targeName
     * @param money
     */
void transfer(String sourceName,String targeName,Float money);

}
package com.xhbjava.service.impl;

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

/**
 * 账户业务层接口实现类
 * 
 * @author mr.wang
 *
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    @Override
    public Account findAccountById(Integer id) {
        // TODO Auto-generated method stub
        return accountDao.findAccountById(id);
    }
    @Override
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改两个账户的金额
        source.setMoney(source.getMoney() - money);// 转出账户减钱
        target.setMoney(target.getMoney() + money);// 转入账户加钱
        // 3.更新两个账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

}

  5》Dao层接口和实现类类

package com.xhbjava.dao;

import com.xhbjava.pojo.Account;

/**
 * 账户持久层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccountDao {
    /**
     * 根据id查询账户
     * 
     * @param id
     * @return
     */
    Account findAccountById(Integer id);

    /**
     * 根据名字查询账户
     * 
     * @param sourceName
     * @return
     */
    Account findAccountByName(String sourceName);

    /**
     * 更新账户
     * 
     * @param source
     */
    void updateAccount(Account source);

}
package com.xhbjava.dao.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.pojo.AccountRowMapper;

/**
 * 用户持久层接口实现类
 * 
 * @author mr.wang
 *
 */
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccountById(Integer id) {
        List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public Account findAccountByName(String sourceName) {
        List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(),
                sourceName);
        if (list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
        }
        return list.get(0);
    }

    @Override
    public void updateAccount(Account source) {
        // TODO Auto-generated method stub
        jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId());
    }

}
package com.xhbjava.pojo;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;
/**
 * 账户的封装类 RowMapper 的实现类
 * @author mr.wang
 *
 */
public class AccountRowMapper implements RowMapper<Account>{

public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
    Account account = new Account();
    account.setId(rs.getInt("id"));
    account.setName(rs.getString("name"));
    account.setMoney(rs.getFloat("money"));
    return account;
}
}

6》Spring文件中配置业务层和持久层以及数据等

<!-- 配置service -->
    <bean id="accountService"
        class="com.xhbjava.service.impl.AccountServiceImpl">
    </bean>
    <!-- 配置dao -->
    <bean id="accountDao" class="com.xhbjava.dao.impl.AccountDaoImpl">
    </bean>
    <!-- 数据库模板配置 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 数据库配置 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

(2)事务配置

   1》配置事务管理器

  在Spring配置文件中进行配置:

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

  2》配置事务通知引用事务管理器

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

       3》配置事务的属性

<!-- 配置事务的通知引用事务管理器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" read-only="false" propagation="REQUIRED" />
            <tx:method name="find*" read-only="true" propagation="SUPPORTS" />
        </tx:attributes>
    </tx:advice>
  指定方法名称:是业务核心方法
  read-only:是否是只读事务。默认 false,不只读。
  isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
  propagation:指定事务的传播行为。
  timeout:指定超时时间。默认值为:-1。永不超时。
  rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
  no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
  4》配置 AOP 切入点表达式
    <!--配置aop -->
    <aop:config>
    <!-- 配置切入点表达式 -->
     <aop:pointcut expression="execution(* com.xhbjava.service.impl.*.*(..))" id="pt1"/>
    <!-- 在 aop:config 标签内部:建立事务的通知和切入点表达式的关系 -->
     <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    </aop:config>

  5》配置切入点表达式和事务通知的对应关系

<!-- 在 aop:config 标签内部:建立事务的通知和切入点表达式的关系 --> 

<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>

(3)测试

测试之前数据库:

调用的方法:

 

 执行测试方法:

 

 

测试后数据库:

 

 4.基于注解方式声明式事务控制

(1)环境搭建

  1》引入依赖包

  略。

  2》配置Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/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">
    <!-- 配置spring创建容器时要扫描的包 -->
    <context:component-scan base-package="com.xhbjava"></context:component-scan>
    <!-- 数据库模板配置 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 数据库配置 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
</beans>

3》创建数据库表和实体类

  略,参照上面即可。

4》创建业务层和Dao层接口和实现类并使用注解让 spring 管理

package com.xhbjava.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

/**
 * 账户业务层接口实现类
 * 
 * @author mr.wang
 *
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public Account findAccountById(Integer id) {
        // TODO Auto-generated method stub
        return accountDao.findAccountById(id);
    }

    @Override
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改两个账户的金额
        source.setMoney(source.getMoney() - money);// 转出账户减钱
        target.setMoney(target.getMoney() + money);// 转入账户加钱
        // 3.更新两个账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

}
package com.xhbjava.dao.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.xhbjava.dao.IAccountDao;
import com.xhbjava.pojo.Account;
import com.xhbjava.pojo.AccountRowMapper;

/**
 * 用户持久层接口实现类
 * 
 * @author mr.wang
 *
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccountById(Integer id) {
        List<Account> list = jdbcTemplate.query("select * from account where id = ? ", new AccountRowMapper(), id);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public Account findAccountByName(String sourceName) {
        List<Account> list = jdbcTemplate.query("select * from account where name = ? ", new AccountRowMapper(),
                sourceName);
        if (list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
        }
        return list.get(0);
    }

    @Override
    public void updateAccount(Account source) {
        // TODO Auto-generated method stub
        jdbcTemplate.update("update account set money = ? where id = ? ", source.getMoney(), source.getId());
    }

}

(2)事务配置

1》配置事务管理器并注入数据源

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

2》在业务层使用@Transactional 注解

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }
    .......
}
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
3》在配置文件中开启 spring 对注解事务的支持
<!-- 开启 spring 对注解事务的支持 -->
     <tx:annotation-driven transaction-manager="transactionManager"/>

不使用XML方式:(之前写过)

@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}

4》测试

略。

5.Transactional 配置项

  在编程式事务允许自定义事务接口一一TransactionDefinition 它可以由 XML 或者注解@Transactional 进行配置,在上面我基于注解方式用到Transactional 配置项。

  下面是它的接口源代码:

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;

/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

  
    @AliasFor("transactionManager")
    String value() default "";

    
    @AliasFor("value")
    String transactionManager() default "";

   
    Propagation propagation() default Propagation.REQUIRED;

    
    Isolation isolation() default Isolation.DEFAULT;

   
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

  
    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

  
    Class<? extends Throwable>[] noRollbackFor() default {};

  
    String[] noRollbackForClassName() default {};

}

通过代码发现他们的配置项不是很多,下面是他的具体配置项说明:

配置项 含义 备注
value
定义事务管理器
它是 Spring JoC 容器里的 Bean id ,这个 Bean 1/li 要实现
接口 Pl atformTransactionManager
transactionManager 
同上 同上
isolation
隔离级别
当一个数据库在多个事务同时存在时出现,默认值取数据库默认隔离级别
propagation   
传播行为
传播行为是方法之间调用的问题。默认值为 Propagation .REQUIRED
timeout 
超时时间
单位为秒,当超时时,会引发异常,默认会导致事务囚滚
readOnly 
是否开启只读事务
默认值为 false
rollbackFor 
回滚事务的异常类定义
也就是只有当方法产生所定义异常时,才回滚事务,否则就提交事务
rollbackForClassName 
回滚事务的异常类名定义
同rolbackFor ,只是使用类名称定义
noRollbackFor 
当产生哪些异常不回滚事务
当产生所定义异常时, Sprin 将继续提交事务  
noRollbackForClassName 
同noRollbackFor
同noRollbackFor ,只是使用类的名称定义 

6.事务定义器

  从注解@Transactional 或者 XML 中我 看到了事务定义器的身影,因此我们有必要讨下事务定义器 TransactionDefinition。接口源码如下:

package org.springframework.transaction;
import java.sql.Connection;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {

//传播行为7个常亮定义 int PROPAGATION_REQUIRED = 0;//默认传播级别 int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6;   //隔离级别 5个定义 int ISOLATION_DEFAULT = -1;//默认级别 int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
   //-1代表永不超时 int TIMEOUT_DEFAULT = -1;   //获取传播行为 int getPropagationBehavior();    //获取隔离级别 int getIsolationLevel(); //事务超时时间 int getTimeout();   //是否只读事务 boolean isReadOnly();   //获取事务定义器的名称 @Nullable String getName(); }

 7.声明式事务的约定流程

   @Transaction注解可以使用在方法或者类上面,在SpringIoC容器初始化时,Spring会读入这个注解或者XML配置的事务信息,并且保存到一个事务定义类里面( TransactionDefinition 接口的子类),以备将来使用。当运行时会让 Spring 拦截注解标注的某 一个方法或者类的所有方法,Spring将编写的代码织入到 AOP 的流程中,然后给出它的约定。

  首先 Spring 通过事务管理器(PlatformTransactionManager 子类)创建事务,与此同时会把事务定义中的隔离级别、超时时间等属性根据配置内容往事务上设置。而根据传播行为配置采取一种特定的策略,Spring 根据配置完,无须编码。启动业务代码,我们知道Spring会通过反射的方式调度开发者的业务代码,但是反射的结果可能是正常返回或者产生异常,那么它给的约定是只要发生异常,井且符合事务定义类回滚条件的,Spring 就会将数据库事务回滚,否则将数据库事务提交,这也是 Spring 自己完成的。在编码过程中发现,我们只 需要编写业务代码和对事务属性进行配置即可,无需代码干预,代码逻辑也更为清晰,更有利于维护。

   声明式事务的流程如下:

 

    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=3)
    public void transfer(String sourceName, String targeName, Float money) {
        // TODO Auto-generated method stub
        // 1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
        // 2.修改两个账户的金额
        source.setMoney(source.getMoney() - money);// 转出账户减钱
        target.setMoney(target.getMoney() + money);// 转入账户加钱
        // 3.更新两个账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        accountDao.updateAccount(target);
    }

二、Spring+MyBatis 组合中使用事务

完成项目结构:

1.创建maven工程

2.创建数据库表

略,沿用之前的表account。

3.环境搭建

(1)pom文件配置,添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xhbjava</groupId>
    <artifactId>Spring02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
         <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.1</version>
        </dependency>
          <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>


        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</project>

(2)配置Spring中bean.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns="http://www.springframework.org/schema/beans"
    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/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:annotation-config />
    <context:component-scan base-package="com.xhbjava.**" />
    <!-- 数据库连接池配置 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl"
            value="jdbc:mysql://localhost:3306/ssm?useSSL=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!-- 集成MyBatis -->
    <bean id="sqlSessionFactory"
        class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
    </bean>
    <!-- 事务管理器配置数据源事务 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 使用注解定义事务 -->
    <tx:annotation-driven
        transaction-manager="transactionManager" />
    <!-- 使用自动扫码对象关系映射 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
        <property name="sqlSessionFactoryBeanName"
            value="sqlSessionFactory"></property>
        <!-- 指定要自动扫描接口的基础包,实现接口 -->
        <property name="basePackage"
            value="com.xhbjava.mapper"></property>
    </bean>
</beans>

(3)创建Account实体类

  沿用之前,略。

(4)搭建 MyBatis 的映射文件 建立 SQL POJO 的关系。

package com.xhbjava.mapper;

import org.springframework.stereotype.Repository;

import com.xhbjava.pojo.Account;

@Repository
public interface AccountMapper {

    public int insertAccount(Account account);

}
<?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.xhbjava.mapper.AccountMapper">
<insert id="insertAccount" parameterType="com.xhbjava.pojo.Account">
    insert into account(name,money)value(#{name},#{money})
</insert>
</mapper>

(5)为了引入这 映射器,配置MyBatis 配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<mappers>
  <mapper resource="com/xhbjava/mapper/AccountMapper.xml" />
</mappers>
</configuration>

(6)添加接口类和实现类

package com.xhbjava.service;

import com.xhbjava.pojo.Account;

/**
 * 账户的业务层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccountService {

    public int insertAccount(Account account);

}
package com.xhbjava.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.xhbjava.mapper.AccountMapper;
import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;
@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private AccountMapper accountMapper =null;
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
    public int insertAccount(Account account) {
        return accountMapper.insertAccount(account);
    }

}
package com.xhbjava.service;

import java.util.List;

import com.xhbjava.pojo.Account;

/**
 * 账户的业务层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccountListService {

    public int insertListAccount(List<Account> accountList);

}
package com.xhbjava.service.impl;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountListService;
import com.xhbjava.service.IAccountService;
@Service
public class AccountListServiceImpl implements IAccountListService {

    @Autowired
    private IAccountService accountService;
    Logger logger = Logger.getLogger(AccountListServiceImpl.class);
    
    
    @Override
    @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
    public int insertListAccount(List<Account> accountList) {
        
        int count = 0;
        for(Account account:accountList) {
            try {
                count +=accountService.insertAccount(account);
            }catch(Exception e) {
                logger.info(e);
            }
            
        }
        return count;
    }

}

(7)log4j配置

### set log levels ###
log4j.rootLogger = DEBUG,stdout

###  \u8F93\u51FA\u5230\u63A7\u5236\u53F0  ###
log4j.logger.org.springframework=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C:%m%n
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%d{yy/MM/dd HH:mm:ss:SSS}]-%l:%m%n

### \u8F93\u51FA\u5230\u65E5\u5FD7\u6587\u4EF6 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=${project}src\\main\\resources\\app.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n

4.测试

package com.xhbjava.test;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountListService;

public class TestSpring {
    public static void main(String args[]) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        IAccountListService accountListService = ctx.getBean(IAccountListService.class);
        List<Account> accountList = new ArrayList<Account>();
        for(int i=0;i<=2;i++) {
            Account account = new Account();
            account.setName("张三"+i);
            account.setMoney(100f+i);
            accountList.add(account);
        }
        int count = accountListService.insertListAccount(accountList);
        System.out.println(count);
    }

}

5.一些注意问题

 (1)错误使用service

package com.xhbjava.controller;

import org.springframework.beans.factory.annotation.Autowired;

import com.xhbjava.pojo.Account;
import com.xhbjava.service.IAccountService;

public class AccountController {
    @Autowired
    private IAccountService accountService = null;
    
    public void errorUseServices() {
        Account account = new Account();
        account.setName("李四");
        account.setMoney(12f);
        accountService.insertAccount(account);
        Account account1 = new Account();
        account1.setName("李四1");
        account1.setMoney(12f);
        accountService.insertAccount(account1);
    }

}

  在上面代码中,Controller 使用 Service 方法时,如果这个 Service 标注有@Transactional ,那么它就会启用一个事务,而 一个Service 方法完成后,它就会释放该事务,所以前后两个insertRole 方法是在两个不同的事务中完成的。这样容易造成第一个插入成功,第二个插入失败,造成数据不一致,这是我们需要注意的。

 

posted on 2020-11-25 16:46  王小码  阅读(148)  评论(0编辑  收藏  举报