springboot-10-整合Mybatis及事务

1.SpringBoot整合Mybatis

  1. 准备数据库
CREATE TABLE `user` (
			  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
			  `name` varchar(128) DEFAULT NULL COMMENT '名称',
			  `phone` varchar(16) DEFAULT NULL COMMENT '用户手机号',
			  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
			  `age` int(4) DEFAULT NULL COMMENT '年龄',
			  PRIMARY KEY (`id`)
			) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
  1. springboot整合mybatis
			<!-- 引入starter-->
					<dependency>
					    <groupId>org.mybatis.spring.boot</groupId>
					    <artifactId>mybatis-spring-boot-starter</artifactId>
					    <version>1.3.2</version>
					    <scope>runtime</scope>			    
					</dependency>
		 			
		 	<!-- MySQL的JDBC驱动包	-->	
		 			<dependency>
						<groupId>mysql</groupId>
						<artifactId>mysql-connector-java</artifactId>
						<scope>runtime</scope>
					</dependency> 
			<!-- 引入第三方数据源 -->		
					<dependency>
						<groupId>com.alibaba</groupId>
						<artifactId>druid</artifactId>
						<version>1.1.6</version>
					</dependency>

配置数据库连接:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc://mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
# msyql-connector-java 8版本,必须按照如下写法,否则连接失败,注意时区设置,默认UTC会导致入库时间不正确
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=lchadmin
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  1. pojo DAO Service Controller编写
package com.example.springbootdemo3.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    
    
    private int id;
    private String name;
    private  String phone;
    private  int age;
    // 指定序列化时的格式和时区,如果不指定时区,反列化之后的时间会比设置的时间慢8小时
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date createTime;

    public User(){
    
    
        super();
    }

    public User(int id, String name, String phone, int age, Date createTime) {
    
    
        this.id = id;
        this.name = name;
        this.phone = phone;
        this.age = age;
        this.createTime = createTime;
    }   // getter  setter省略
}

Mapper接口,完成增删改查:

package com.example.springbootdemo3.mapper;

import com.example.springbootdemo3.domain.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
    
    

    // 保存对象,获取数据库自增id,keyColumn:数据库自增主键名
    @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
    @Insert("insert into users(name,phone,create_time,age) values (#{name},#{phone},#{createTime},#{age})")
    Integer insert(User user);

    @Select("select * from users")
    @Results({
    
     @Result(column = "create_time",property = "createTime")})
    List<User> getAllUsers();

    @Select("select * from users where id=#{id}")
    @Results({
    
     @Result(column = "create_time",property = "createTime")})
    User findById(Integer id);

    @Update("update users set name=#{name} where id=#{id}")
    void update(User user);

    @Delete("delete from users where id=#{userId}")
    void delete(Integer userId);
}

UserService 接口及实现:

package com.example.springbootdemo3.service;
import com.example.springbootdemo3.domain.User;
public interface UserService {
    
    
    Integer  addUser(User user);
    void updateUser(User user);
}
package com.example.springbootdemo3.service.impl;

import com.example.springbootdemo3.domain.User;
import com.example.springbootdemo3.mapper.UserMapper;
import com.example.springbootdemo3.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    
    
    @Autowired
    private UserMapper userMapper;

    @Override
    public Integer addUser(User user) {
    
    
        return userMapper.insert(user);
//        return 1;
    }

    @Override
    public void updateUser(User user) {
    
    
        userMapper.update(user);
    }
}

API接口:

package com.example.springbootdemo3.controller;

import com.example.springbootdemo3.domain.User;
import com.example.springbootdemo3.service.UserService;
import com.example.springbootdemo3.service.impl.UpdateUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;

@Controller
public class UserController {
    
    

    @Autowired
    private UserService userService;
    @Autowired
    UpdateUserService updateUserService;

    @RequestMapping("/add")
    @ResponseBody
    public Object addUser() {
    
    
        User user = new User();
        user.setAge(18);
        user.setName("lucy");
        user.setPhone("18659996306");
        user.setCreateTime(new Date());
        userService.addUser(user);
        // 获取本次插入数据的自增id
        return "当前插入的数据在数据库中的自增id值为:" + user.getId();
    }
}
  1. 启动类,配置mapper扫描包路径:@MapperScan(“com.example.springbootdemo3.mapper”)
package com.example.springbootdemo3;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.springbootdemo3.mapper")  // 配置扫描包
public class Application {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Application.class, args);
    }
}

测试结果
在这里插入图片描述

2.事务以及事务的嵌套执行

  1. 数据库事务的四大隔离级别
  2. 事务的传播机制
  3. 单机事务测试
    (1)单机单事务测试,在controller中加上下面的API进行测试,
/**
     * mysql单机事务测试 :4种隔离级别,6种传播机制
     * MySQL事务默认隔离级别:read commit
     * mysql默认事务传播机制 :REQUIRED
     *
     * @return
     */
    @RequestMapping("/testTransaction")
    @ResponseBody
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Object testTransaction() {
    
    
        User user = new User();
        user.setAge(21);
        user.setName("lucy");
        user.setPhone("18659996306");
        user.setCreateTime(new Date());
        userService.addUser(user);
        int i = 1 / 0;
        return "success";
    }

结果: 数据库没有新增数据,页面报错
(2)同一个servcie内部的嵌套事务
将testTransaction()中的异常去掉,在另外一个方法中调用userService.addUser() 和updateUser()

  @RequestMapping("/testTransaction")
    @ResponseBody
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Object testTransaction() {
    
    
        User user = new User();
        user.setAge(21);
        user.setName("lucy");
        user.setPhone("18659996306");
        user.setCreateTime(new Date());
        userService.addUser(user);
//        int i = 1 / 0;
        return "success";
    }

    /**
     * 事务嵌套测试:  在同一个servcie中的两个方法,不论事务的传播机制选的是什么,只要有一个方法中抛出了异常,
     * 相关调用的方法都会进行回滚!这里 testTransactions 或者updateUser方法中,有一个方法抛异常,整个方法都会进行回滚
     * 因为同一个service中,spring只会开启一个事务!!!要想测试REQUIRES_NEW,需要将被调用方法写在单独的service中
     * @return
     */
    @RequestMapping("/testTransaction1")
    @ResponseBody
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Object testTransactions() {
    
    
        User user = new User();
        user.setAge(19);
        user.setName("lucy");
        user.setPhone("18659996306");
        user.setCreateTime(new Date());
        userService.addUser(user);
        this.updateUser();
        int i = 1/0;
        return "success";
    }

测试结果:
在同一个servcie中的两个方法,不论事务的传播机制选的是什么,只要有一个方法中抛出了异常, 相关调用的方法都会进行回滚!这里 testTransactions 或者updateUser方法中,有一个方法抛异常,整个方法都会进行回滚,因为同一个service中,spring只会开启一个事务!要想测试REQUIRES_NEW,需要将被调用方法写在单独的service中

(3)不同servcie之间的事务嵌套
首先定义一个UpdateUserService 用来更新用户信息,这个servcie类的方法,事务隔离级别设置为REQUIRES_NEW

package com.example.springbootdemo3.service.impl;

import com.example.springbootdemo3.domain.User;
import com.example.springbootdemo3.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 更新用户
 * Propagation.REQUIRES_NEW:
 * 新建事务,如果当前存在事务,把当前事务挂起, 两个事务之间没有关系,一个异常,一个提交,不会同时回滚
 * 如果调用当前方法的service中开启了事务,这里的事务跟调用者的事务没有关系
 *
 */
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class UpdateUserService {
    
    
    @Autowired
    private UserMapper userMapper;

    /**
     * 测试事务嵌套—— 被调用方法开启新的事务:外层异常不影响这里
     * @param user
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUser(User user){
    
    
        userMapper.update(user);
//        如果这个被调用的方法中有异常,则调用的地方和这里都会回滚,如果是调用的地方有异常,这里执行没有异常,则仅仅是调用的方法中的事务会回滚
      //  int i = 1/0;
    }
}

跨service 事务嵌套,外层事务隔离级别为REQUIRED,内存事务隔离级别为REQUIRES_NEW ,事务回滚分两种情况:
(1)外层事务有异常,内存事务没有异常—— 外层事务回滚,内层事务不受影响
在controller中,编写一个接口方法,它的事务传播机制为REQUIRED, 这个方法中,首先新增一个User,然后调用updateUserService.updateUser() 方法去更新一个已有的user(这时假定updateUserService.updateUser方法中不抛异常,int i = 1/0; 注释掉) 在这之后,手动抛出一个异常,测试内层事务是否会和外层事务一起回滚;

 /**
     * 测试跨service 事务嵌套
     * testTransaction2方法传播机制为REQUIRED,updateUserService.updateUser(user1)方法的事务传播机制为REQUIRES_NEW
     * 当testTransaction2方法中抛出异常,不影响updateUser方法,updateUser正常执行
     * 内层方法updateUser抛出异常,内层方法和外层方法一起回滚!
     * 参考: https://www.bbsmax.com/A/kvJ36Dknzg/
     * @return
     */
    @RequestMapping("/testTransaction2")
    @ResponseBody
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public Object testTransaction2() {
    
    
        User user = new User();
        user.setAge(20);
        user.setName("jack");
        user.setPhone("18659996306");
        user.setCreateTime(new Date());
        userService.addUser(user);
        User user1 = new User();
        user1.setId(9);
        user1.setName("testTransaction2");
        updateUserService.updateUser(user1);
        int i = 1/0;
        return "success";
    }

数据库:
在这里插入图片描述
可以看到,
在这里插入图片描述
(2)外层事务无异常,内存事务有异常 —— 内层事务和外层事务一起回滚
在updateUserService.updateUser中抛出异常(updateUserService.updateUser方法里面 加上 int i = 1/0;),testTransaction2() 中不抛异常(int i = 1/0;注释掉),将数据库中id=9的这条数据的name值改掉,再次调用http://localhost:8080/testTransaction2接口,查看事务的回滚情况:
数据库既没有新增name=jack的user, 也没有修改id=9的user,说明,内层事务和外层事务一起回滚了。

  1. 分布式事务(分布式事务:二阶提交 ,最终一致性,通过消息队列解决)
    —— 待添加

猜你喜欢

转载自blog.csdn.net/weixin_41300437/article/details/107775122