Spring集成Mybatis和Spring事务

1,775 阅读6分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

Spring集成Mybatis

集成思路

Spring能集成很多的框架,是Spring的优势,通过集成其他框架试开发更简单方便,集成使用的SPring的IOC功能

要使用Mybatis步骤

要使用mybatis就要创建mybatis框架里的某些对象,使用这些对象就能使用mybatis的功能了,到底需要哪些呢,我们可以把这些对象交给Spring来管理,需要使用在容器里拿就可以了

  • 需要有dao的代理对象
  • 需要SQLSessionFactory,创建sqlSessionFactory对象,才能使用openSession()得到SqlSession对象
  • 数据源DataSource对象

整合mybatis

生成数据库表

CREATE TABLE `user` (
  `id` int(20) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

加入依赖xml

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
<!--spring依赖-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.12</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.12</version>
  </dependency>
  <!--spring事务-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.12</version>
  </dependency>
<!--springjdbc-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.12</version>
  </dependency>
<!--mybatis依赖-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
  </dependency>
<!--Mybatis-spring依赖-->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
  </dependency>
<!--mysql连接数据库依赖-->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.27</version>
  </dependency>
<!--druid依赖-->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
  </dependency>
</dependencies>

实体类

public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", pwd='" + pwd + ''' +
                '}';
    }
}

UserMapper.java

@Mapper
public interface UserMapper {
    public int InsertUser(User user);
    public User finfdById(Integer id);
}

UserMapper.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">
<!--绑定mapping接口-->

<mapper namespace="org.study.Mapper.UserMapper">

<insert id="InsertUser" >
    INSERT INTO user
        ( id, name,pwd)
    VALUES(#{id}, #{name}, #{pwd});
</insert>
    <select id="finfdById" resultType="user">
        select  * from user where id= #{id}
    </select>
</mapper>
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void insertUser(User user) {
        userMapper.InsertUser(user);
    }
    @Override
    public User findById(Integer id) {
        User user = userMapper.finfdById(id);
        return user;
    }
}
@Service
public interface UserService {

    public void insertUser(User user);

    public User findById(Integer id);
}

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>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
   <typeAliases>
        <package name="org.study.domain"/>
    </typeAliases>

    <mappers>
       <package name="org.study.Mapper"/>
    </mappers>
</configuration>

spring配置文件

<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--声明数据源-->

    <context:annotation-config />
    <context:component-scan base-package="org.study"/>
    <bean id="MyDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="name" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--声明SqlSessionFactoryBean 在这个类的内部创建 SqlSessionFactory-->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--指定数据源-->
        <property name="dataSource" ref="MyDataSource"></property>
        <!--指定mybatis主配置文件-->
        <property name="configLocation" value="classpath:mybatsi-config.xml"></property>
    </bean>

    <!--声明MapperScannerConfigurer-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactoryBean-->
        <property name="sqlSessionFactoryBeanName" value="factory"></property>
        <!--知道包名-->
        <property name="basePackage" value="org.study.Mapper"></property>
    </bean>


</beans>

测试类

@Test
public void shouldAnswerWithTrue()
{
    String config ="application.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    UserService service = (UserServiceImpl) applicationContext.getBean("userServiceImpl");
    User user1 = new User();
    user1.setId(13);
    user1.setName("liming");
    user1.setPwd("12345");
    service.insertUser(user1);
    User user = service.findById(13);
    System.out.println(user);

}

输出结果 image.png

Spring事务

概述

事务是一些列SQL语句的集合,是多条SQL要么都成功要么都失败。

什么时候使用事务

一个操作需要多条Sql语句一块完成,操作才能成功

事务在哪里说明

在业务方法上边

public class dao(){
   public void insertA();
   public void insertB():
}

public void Service(){

    @Autowired
    private dao dao1;

//事务下在这里
    public void insertAB(){
        dao1.insertA();
        dao1.insertB();
    }

}

Spring事务管理器

不同的数据库访问技术,处理事务是不同的,Spring统一管理事务,把不同的数据访问技术的事务处理统一起来,使用Spring的事务管理器,管理不同数据库访问技术处理事务,只需要掌握spring的事务处理一个方案,可以实现使用不同数据库访问技术的事务管理。

spring框架使用事务管理器管理对象,事务管理器的接口是PlatformTransacationManger,定义了事务的操作,主要是commit()rollback()事务管理器有很多实现类,一种数据库访问计数有一个实现类,有实现类具体完成事物的提交,回滚。 jdbc和mmybatis的事务管理器是DataSourceTranactionManager。hibernate事务管理器是hibernateTranactionManager

老师画的一张图,spring管理事务的工作方式,业务代码正常执行局提交了,运行时出现异常就回滚了

image.png 业务代码正常执行局提交了,运行时出现异常就回滚了

异常分类

  • Error 严重错误,回滚事务
  • Exception 异常类,可以处理的异常
  1. 运行时异常 : RuntimeException和它的子类都是运行时异常,在程序执行中抛出的异常,常见的有 NullPointException空指针异常,IndexOutOfBoundsException数组越界异常ClassCastException强制装换异常
  2. 受查异常: 编写java代码的时候,必须出来的异常,如IOException。SQLException

运行方法中只要出现了运行时异常事务回滚,其他情况(正常执行方法,受查异常)提交事务

事务使用的是AOP的环绕通知

环绕通知: 可以在目标方法的前后都机上增强,不需要修改代码,在业务代码前开启事务,在业务代码后提交事务,出现异常Catch回滚事务

事务定义接口 TransactionDefinition

定义了三类常量,定义了有关事务控制的属性

  • 隔离级别
  • 传播行为
  • 事物的超时

隔离级别

隔离级别:控制事物之间的影响程度

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

  • DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ(可重复读);Oracle默认为 READ_COMMITTED。(读已提交)
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。

超时时间

以秒为单位 ,默认-1,表示一个业务方法最长的执行时间,到时见没有执行完毕,会回滚事务

传播行为

业务方法在执行时,事务在方法间的传递和使用,可以标志方法有无事务,有七个值PROPAGATION_XXXX开头

  • PROPAGATION_REQUIRED 默认传播行为,方法执行时,如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务,在新事务里执行

  • PROPAGATION_SUPPORTS 支持,如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

  • PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。


  • PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

  • PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

  • PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常

  • PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

代码测试--不加事务

模拟买东西订单表添加商品,库存表减库存

准备两张表

CREATE TABLE `sale` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `gid` int(11) DEFAULT NULL,
  `nums` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8

CREATE TABLE `goods` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL,
  `price` float DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8

实体类

@Data
public class Sale {

    private Integer id;
    private Integer  gid ;
    private Integer nums;
}
@Data
public class Goods {

    private Integer id;

    private String name;

    private Integer amount;

    private float price;
}

spring配置文件

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--声明数据源-->

    <context:annotation-config />
    <context:component-scan base-package="com.study"/>
    <bean id="MyDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="name" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--声明SqlSessionFactoryBean 在这个类的内部创建 SqlSessionFactory-->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--指定数据源-->
        <property name="dataSource" ref="MyDataSource"></property>
        <!--指定mybatis主配置文件-->
        <property name="configLocation" value="classpath:mybatsi-config.xml"></property>
    </bean>

    <!--声明MapperScannerConfigurer-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactoryBean-->
        <property name="sqlSessionFactoryBeanName" value="factory"></property>
        <!--知道包名-->
        <property name="basePackage" value="com.study.mapper"></property>
    </bean>

</beans>

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>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
   <typeAliases>
        <package name="com.study.domain"/>
    </typeAliases>

    <mappers>
       <package name="org.study.Mapper"/>
    </mappers>
</configuration>

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">
<!--绑定mapping接口-->

<mapper namespace="com.study.mapper.saleMapper">

    <insert id="buyGoods" >
        insert into sale  ( gid, nums)
        VALUES(#{gid}, #{nums});
    </insert>

</mapper>
@Mapper
public interface saleMapper {

    public void buyGoods(Sale sale1);
}
<?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">
<!--绑定mapping接口-->

<mapper namespace="com.study.mapper.goodsMapper">

    <select id="findById" resultType="goods">
        select * from goods where id=#{id}
    </select>

    <update id="updateAmount" >
        update goods set amount = amount -  #{amount}  where id= #{id}
    </update>

</mapper>
@Mapper
public interface goodsMapper {
    public Goods findById(Integer id);

    public void updateAmount(Goods good);
}

业务方法

public interface BuyService {

    void Buy(Integer id,Integer num);

}
@Autowired
private goodsMapper goodsMapper1;

@Autowired
private saleMapper saleMapper1;


@Override
public void Buy(Integer id, Integer num) {

    System.out.println("开始买了");

    Sale sale = new Sale();
    sale.setGid(id);
    sale.setNums(num);
    saleMapper1.buyGoods(sale);

    Goods byId = goodsMapper1.findById(sale.getGid());

    if (byId == null){
        throw new NullPointerException("商品不存在");
    } else if(byId.getAmount()<sale.getNums()){
        throw new MyException("库存不足");
    }

    Goods goods = new Goods();
    goods.setAmount(sale.getNums());
    goods.setId(sale.getGid());
    goodsMapper1.updateAmount(goods);


}

测试类

public void shouldAnswerWithTrue()
{
    String config ="application.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    BuyService service = (BuyService) applicationContext.getBean("buyServiceImpl");
    service.Buy(1001,1000);

}

以上代码我没加事务的情况下 去购买没有的产品,报错但是第一步订单表的信息还是会保存。

Spring框架使用自己的注解@Transaction控制事务

@Transaction使用注解的属性控制事务,隔离级别,传播行为,超时时间

@Transaction的属性

  • propagation : 事物的传播行为,他使用的Propagation类的枚举值 Propagation.REQUIRED
  • isolation : 隔离级别 使用Isolation枚举类表示隔离级别 默认Isolation.DEFAULT
  • readOnly : Boolean类型的值 标识数据库操作是不是只读,默认false
  • timeout : 事务超时,默认是-1 整数值单位是秒,例如 timeout =20
  • rollBackFor :表示回滚的异常类型
  • rollBackForClassName : 回滚的异常类列表。值时异常类名称,String类型的值
  • noRollackFor :不需要回滚的数据异常,是class类型
  • noRollBackForClassName : 不需要回滚的数据异常,是String类型 位置
  • 在业务方法的上边,在public方法上边
  • 在类的上边 特点
  • Spring自己的事务管理
  • 适合中小项目
  • 使用方便

使用注解实现事务

在Spring配置文件里声明事务管理器并开启注解

    <!--声明事务控制器-->
<bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--指定事务操作的数据源-->
    <property name="dataSource" ref="MyDataSource"></property>
 </bean>

    <!--开启事务注解驱动,告诉框架使用注解管理事务
        transaction-manager:指定事务管理器的id
        proxy-target-class:为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性
    -->
    <tx:annotation-driven transaction-manager="TransactionManager" proxy-target-class="true"></tx:annotation-driven>

在业务代码上添加@Transactional注解并配置属性,直接写@Transactional也可以实现事务,都使用默认值。rollbackFor回滚的异常类型,指定的话先根据指定的找,找不到找是不是RuntimeException的子类都会回滚,那这个属性是不是有点多余,当我们要指定受查异常时就可以使用,还有一个属性noRollackFor,可以指定什么异常不需要回滚

@Transactional(propagation = Propagation.REQUIRED,
                isolation = Isolation.DEFAULT,
                timeout = 20,
                readOnly = false,
                rollbackFor ={NullPointerException.class,MyException.class}
)

声明式事务

<!--声明式事务-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="MyDataSource"></property>
</bean>
<!--声明业务方法的事务属性(隔离级别,传播行为,超时)-->
<tx:advice id="Advice" transaction-manager="transactionManager">
    <!--给具体的业务方法增加事物的说明-->
    <tx:attributes>
        <!--
            给具体的业务方法添加其他事务属性
            name:业务方法名称 配置name的值
                1 业务方法的名称
                2 带有部分通配符的业务方法的名称
                3 使用*
            propagation : 指定传播行为的值
            isolation   :隔离级别
            read-only   :是否只读
            timeout     :超时时间
            rollback-for :指定回滚的异常类型
        -->
        <tx:method name="Buy" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="20"/>
        <!--有命名规则的业务方法-->
        <tx:method name="add*" propagation="REQUIRED"></tx:method>
        <tx:method name="update*" propagation="REQUIRED"></tx:method>
        <tx:method name="delect*" propagation="REQUIRED"></tx:method>
        <!--以上方法以外的* -->
        <tx:method name="*" propagation="SUPPORTS" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>

<aop:config>
    <!--声明切入点表达式
        expression :声明那些类中的方法参与事务
        id 名称
    -->
    <aop:pointcut id="servicePoint" expression="execution(* *..service..*.*(..))"/>
    <!--切入点表达式和事务通知-->
    <aop:advisor advice-ref="Advice" pointcut-ref="servicePoint"></aop:advisor>
</aop:config>