1.SpringBoot整合Mybatis
- 准备数据库
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;
- 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
- 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();
}
}
- 启动类,配置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)单机单事务测试,在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,说明,内层事务和外层事务一起回滚了。
- 分布式事务(分布式事务:二阶提交 ,最终一致性,通过消息队列解决)
—— 待添加