Spring集成Mybatis和Spring事务

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

Spring集成Mybatis

集成思路

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

要使用Mybatis步骤

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

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

整合mybatis

生成数据库表

1
2
3
4
5
6
sql复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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>

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
typescript复制代码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

1
2
3
4
5
java复制代码@Mapper
public interface UserMapper {
public int InsertUser(User user);
public User finfdById(Integer id);
}

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@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;
}
}
1
2
3
4
5
6
7
java复制代码@Service
public interface UserService {

public void insertUser(User user);

public User findById(Integer id);
}

Mybatis配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xml复制代码<?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配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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.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>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ini复制代码@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语句一块完成,操作才能成功

事务在哪里说明

在业务方法上边

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码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 属性执行

代码测试–不加事务

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

准备两张表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码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

实体类

1
2
3
4
5
6
7
vbnet复制代码@Data
public class Sale {

private Integer id;
private Integer gid ;
private Integer nums;
}
1
2
3
4
5
6
7
8
9
10
11
vbnet复制代码@Data
public class Goods {

private Integer id;

private String name;

private Integer amount;

private float price;
}

spring配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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" 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配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xml复制代码<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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>
1
2
3
4
5
java复制代码@Mapper
public interface saleMapper {

public void buyGoods(Sale sale1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.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>
1
2
3
4
5
6
java复制代码@Mapper
public interface goodsMapper {
public Goods findById(Integer id);

public void updateAmount(Goods good);
}

业务方法

1
2
3
4
5
csharp复制代码public interface BuyService {

void Buy(Integer id,Integer num);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
scss复制代码@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);


}

测试类

1
2
3
4
5
6
7
8
ini复制代码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配置文件里声明事务管理器并开启注解

1
2
3
4
5
6
7
8
9
10
11
xml复制代码    <!--声明事务控制器-->
<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,可以指定什么异常不需要回滚

1
2
3
4
5
6
ini复制代码@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
timeout = 20,
readOnly = false,
rollbackFor ={NullPointerException.class,MyException.class}
)

声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
xml复制代码<!--声明式事务-->
<!--声明事务管理器-->
<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>

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%