Springboot集成Mybatis
Mybatis作为持久层框架,是现在主流的框架, 本文只关注Springboot和Mybatis之间的构建,不关心Mybatis框架如何时使用,如果需要详细了解Mybatis的请绕道,后续可能我也会研究这个框架
一:构建springboot-mybatis
1:pom.xml
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.geek.calvin</groupId>
<artifactId>springboot-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2:application.yml
server:
port: 8080
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
hikari:
connection-timeout: 30000
mybatis:
mapper-locations: classpath:/mybatis/mapper/*.xml
3: ResultEntity.java
本来不想贴这个,但是怕新手丢了,不知道如何做,勿喷
**
* 请求返回的消息body
* @author Calvin
* @date 2019/07/29
*/
public class ResultEntity {
/**
* 返回状态
*/
private int code;
/**
* 消息
*/
private String msg;
/**
* 消息body
*/
private Object data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
二:业务代码编写
1:StudentController.java
/**
* @author Calvin
* @date 2019/07/29
*/
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private StudentService studentService;
/**
* 查找所有的学生
* @return
*/
@RequestMapping("/list")
public ResultEntity listStudent(){
List<Student> students = studentService.listAll();
ResultEntity entity = new ResultEntity();
entity.setCode(200);
entity.setMsg("请求成功");
entity.setData(students);
return entity;
}
}
2:StudentService.java
/**
* @author Calvin
* @date 2019/07/29
*/
public interface StudentService {
/**
* 查询学生列表
* @return
*/
List<Student> listAll();
}
3:StudentServiceImpl.java
/**
* @author Calvin
* @date 2019/07/29
*/
@Service
public class StudentServiceImpl implements StudentService{
@Autowired
private StudentMapper studentMapper;
@Override
public List<Student> listAll() {
return studentMapper.listAll();
}
}
4: Student.java
/**
* @author Calvin
* @date 2019/07/29
*/
public class Student {
/**
* 学号
*/
private String id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private int gender;
/**
* 入学时间
*/
private Date enterTime;
/**
* 年龄
*/
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public Date getEnterTime() {
return enterTime;
}
public void setEnterTime(Date enterTime) {
this.enterTime = enterTime;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5:StudentMapper.java
**
* @author Calvin
* @date 2019/07/29
*/
@Mapper
public interface StudentMapper {
/**
*
* @return
*/
List<Student> listAll();
}
6:StudentMapper.xml
<mapper namespace="com.geek.calvin.mapper.StudentMapper">
<resultMap id="student" type="com.geek.calvin.entity.Student">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="gender" property="gender"/>
<result column="age" property="age"/>
<result column="enter_time" property="enterTime"/>
</resultMap>
<select id="listAll" resultMap="student">
select id, name, gender, age, enter_time from student where delete_flag = 0
</select>
</mapper>
三:数据库
-- create table DDL
DROP TABLE IS EXISTS 'student';
CREATE TABLE `student` (
`id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`gender` int(1) DEFAULT NULL,
`enter_time` datetime DEFAULT NULL,
`age` int(2) DEFAULT NULL,
`delete_flag` int(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
-- record
INSERT INTO STUDENT VALUES(1, "Calvin", "1", str_to_date('2009-09-01', '%Y-%m-%d'), 26, 0);
四:运行结果
chrome中打开localhost:8080/student/list
五:相关配置
1:打印sql日志
在application.yml中,配置 loggin.level.#{mapper接口所在包,如果有多个,逗号隔开,或者使用通配符} 末尾给值 debug
logging:
level:
com.geek.calvin.mapper: debug
如果是propertie文件就更好办了
loggin.level.com.geek.calvin.mapper=debug
2:PageHelper插件
<!-- pagehelper分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
改造StudentController.listStudent
/**
* 查找所有的学生
* @return 根据分页信息查询学生列表
*/
@RequestMapping("/list")
public ResultEntity listStudent(@RequestParam(value = "pageNum", defaultValue = "1", required = false) int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10",required = false) int pageSize){
PageInfo<Student> students = studentService.listAll(pageNum, pageSize);
ResultEntity entity = new ResultEntity();
entity.setCode(200);
entity.setMsg("请求成功");
entity.setData(students);
return entity;
}
改造StudentService和impl
@Override
public PageInfo<Student> listAll(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Student> students = studentMapper.listAll();
PageInfo<Student> pageInfo = new PageInfo<Student>(students);
return pageInfo;
}
结果:
控制台sql
[nio-8080-exec-7] c.g.c.m.StudentMapper.listAll_COUNT : ==> Preparing: SELECT count(0) FROM student WHERE delete_flag = 0
[nio-8080-exec-7] c.g.c.m.StudentMapper.listAll_COUNT : ==> Parameters:
[nio-8080-exec-7] c.g.c.m.StudentMapper.listAll_COUNT : <== Total: 1
[nio-8080-exec-7] c.g.calvin.mapper.StudentMapper.listAll : ==> Preparing: select id, name, gender, age, enter_time from student where delete_flag = 0 LIMIT ?
[nio-8080-exec-7] c.g.calvin.mapper.StudentMapper.listAll : ==> Parameters: 10(Integer)
[nio-8080-exec-7] c.g.calvin.mapper.StudentMapper.listAll : <== Total: 1
3:mybatis-plus
https://mp.baomidou.com/guide/
pom.xml中增加依赖
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
application.yml中
# 删除原来mybatis的配置,修改为mybatis-plus的配置
# mybatis:
# mapper-locations: classpath:/mybatis/mapper/*.xml
mybatis-plus:
mapper-locations: classpath:/mybatis/mapper/*.xml
新增 MybatisPlusConfig.java作为mybatis-plus配置类
/**
* mybatis-plus配置类
* @author Calvin
* @date 2019/07/29
*/
@Configuration
@MapperScan("com.geek.calvin.mapper")
public class MybatisPlusConfig {
/**
* 分页插件,摘自官网
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// paginationInterceptor.setLimit(你的最大单页限制数量,默认 500 条,小于 0 如 -1 不受限制);
return paginationInterceptor;
}
}
新增CourseController.java课程的接口类
/**
* 课程的接口
* @author Calvin
* @date 2019/07/29
*/
@RestController
@RequestMapping("/course")
public class CourseController {
@Autowired
private CourseService courseService;
@RequestMapping("/list")
public ResultEntity listCourse(@RequestParam(value = "page",defaultValue = "1", required = false) int page,
@RequestParam(value = "pageSize", defaultValue = "10",required = false) int pageSize){
IPage<Course> courseIPage = courseService.listAll(page, pageSize);
ResultEntity entity = new ResultEntity();
entity.setData(courseIPage);
entity.setMsg("success");
entity.setCode(200);
return entity;
}
@RequestMapping("/add")
public ResultEntity create(Course course){
ResultEntity resultEntity = new ResultEntity();
resultEntity.setCode(200);
resultEntity.setMsg("success");
resultEntity.setData(courseService.create(course));
return resultEntity;
}
}
CourseService.java & CourseServiceImpl.java
**
* 课程的service, 逻辑服务
* @author Calvin
* @date 2019/07/29
* @see com.geek.calvin.controller.CourseController
*/
public interface CourseService {
/**
* 根据分页查询
* @param page
* @param pageSize
* @return
*/
IPage<Course> listAll(int page, int pageSize);
/**
* 新建一门课程
* @param course 要添加的课程form
* @return
*/
Course create(Course course);
}
**
* @author Calvin
* @date 2019/07/29
*/
@Service
public class CourseServiceImpl implements CourseService {
@Autowired
private CourseMapper courseMapper;
@Override
public Course create(Course course) {
courseMapper.insert(course);
return course;
}
@Override
public IPage<Course> listAll(int currentPage, int pageSize) {
IPage page = new Page();
page.setCurrent(currentPage);
page.setSize(pageSize);
return courseMapper.selectPage(page, null);
}
定义一个空的没有方法的CourseMapper
/**
* @author Calvin
* @date 2019/07/29
*/
@Mapper
public interface CourseMapper extends BaseMapper<Course>{
/**
* 此处定义这个是为了方便自己方法,如果不打算写sql,直接用mybatis-plus中的Wrapper进行查询的
* 可以直接让自己的service继承BaseMapper,少一个类,虽然我这里没有写
*/
}
课程实体类 Course.java
/**
* 课程实体类
* @author Calvin
* @date 2019/07/29
*/
@TableName("course")
public class Course {
/**
* uuid
*/
@TableId(type = IdType.UUID)
private String id;
/**
* 课程名
*/
@TableField
private String courseName;
/**
* 年级
*/
@TableField
private int grade;
/**
* 节数
*/
@TableField
private int lessonsCount;
/**
* 分类 1:主修 2:选修
*/
@TableField
private int classify;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getLessonsCount() {
return lessonsCount;
}
public void setLessonsCount(int lessonsCount) {
this.lessonsCount = lessonsCount;
}
public int getClassify() {
return classify;
}
public void setClassify(int classify) {
this.classify = classify;
}
}
创建表的sql
create table course(
id varchar(40) PRIMARY key ,
course_name varchar(40) comment '课程名称' ,
grade int(2) comment '年级',
lessons_count int(2) comment '课程节数' ,
classify int(2) comment '分类, 1:入门 2:进阶'
);
这里不使用代码生成器,是觉得代码生成器生成的文件比较繁重,有需要的同学自行研究
add接口 测试调用
//http://localhost:8080/course/add?courseName=设计模式之禅&classify=1&lessonsCount=30&grade=2
{
"code": 200,
"msg": "success",
"data": {
"id": "b2ffe8fd6c7bae770e3e87da2cd65f73",
"courseName": "设计模式之禅",
"grade": 2,
"lessonsCount": 30,
"classify": 1
}
}
list接口调用测试
//http://localhost:8080/course/list
{
"code": 200,
"msg": "success",
"data": {
"records": [
{
"id": "67ab9cea401ab6d74d0cfb1e289d20ce",
"courseName": "计算机科学与技术",
"grade": 1,
"lessonsCount": 20,
"classify": 1
},
{
"id": "992f20aed79322e5bb33bd9c21fd77e4",
"courseName": "架构之美",
"grade": 1,
"lessonsCount": 20,
"classify": 1
},
{
"id": "a397b80c6d1c7c9d25b6919066c3e97e",
"courseName": "Docker技术实战",
"grade": 2,
"lessonsCount": 30,
"classify": 1
},
{
"id": "ae7fe9d9068fbbaa0eb7dbe4150d009a",
"courseName": "java从入门到放弃",
"grade": 1,
"lessonsCount": 20,
"classify": 1
},
{
"id": "b2ffe8fd6c7bae770e3e87da2cd65f73",
"courseName": "设计模式之禅",
"grade": 2,
"lessonsCount": 30,
"classify": 1
},
{
"id": "e0bce26b0a94f27a0c0b154f8cc53e20",
"courseName": "深入浅出微服务",
"grade": 2,
"lessonsCount": 30,
"classify": 2
}
],
"total": 6,
"size": 10,
"current": 1,
"orders": [
],
"searchCount": true,
"pages": 1
}
}
此时依然可以使用student/list接口
或许在分页这块,sql中mybatis-plus做的更简单,是对于表进行了
select count(1) from table_name where conditions
但是如果对分页要求比较高的,还是使用page-helper插件; 老版本的page-helper插件是把你要查询的语句,用一个select count包起来查询一遍的, 新版本这里没有做测试,不知是不是优化了
// http://localhost:8080/student/list
{
"code": 200,
"msg": "请求成功",
"data": {
"total": 1,
"list": [
{
"id": "1",
"name": "Calvin",
"gender": 1,
"enterTime": "2009-09-01T00:07:15.000+0000",
"age": 26
}
],
"pageNum": 1,
"pageSize": 10,
"size": 1,
"startRow": 1,
"endRow": 1,
"pages": 1,
"prePage": 0,
"nextPage": 0,
"isFirstPage": true,
"isLastPage": true,
"hasPreviousPage": false,
"hasNextPage": false,
"navigatePages": 8,
"navigatepageNums": [
1
],
"navigateFirstPage": 1,
"navigateLastPage": 1
}
}
更多关于Mybatis-plus的详情,关注https://mp.baomidou.com 官网网站,并且官网对mybatis-plus和springboot、springmvc的集成都有很多解释
4:控制事务
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 比如发生删除动作,主表信息被删除,详情表一定也要删除,如果主表或者详情表删除发生失败,那么主表和详情表的两条数据应该都没有被删除。
一致性(Consistency) 事务前后数据的完整性必须保持一致。 甲同学给乙同学转账,甲同学账户扣款200元,乙同学账户应当增加200元,否则一致性出现问题。
隔离性(Isolation) 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。管理员A给一个账户增加1000个积分的同时管理员B给这个账户增加500个积分,那么最后这个账户应当被增加1500积分。
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。 即使机器坏了,或者断电,宕机,当数据库恢复或者重新开机后,被修改成功记录的一定是修改后的数据,没有修改成功的,也一定要保证是之前没有被修改的数据。
在现在主流的数据库中,持久性基本可以被保证,所以编码过程中只需要关注原子性、一致性、隔离性,原子性,此处不做这种深入的研究,只演示mybatis框架中如何进行事务控制
TransactionController.java
/**
* 事务控制演示
* @author Calvin
* @date 2019/07/29
*/
@RestController
@RequestMapping("/transaction")
public class TransactionController {
@Autowired
private TransActionService transactionService;
/**
* 演示自动控制事务
* @return
*/
@GetMapping("/auto")
public ResultEntity autoTransaction(){
transactionService.showTransaction();
ResultEntity resultEntity = new ResultEntity();
resultEntity.setCode(200);
resultEntity.setMsg("success");
return resultEntity;
}
}
TransactionServiceImpl.java
/**
* @author Calvin
* @date 2019/07/29
*/
@Service
public class TransactionServiceImpl implements TransActionService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private CourseMapper courseMapper;
/**
* 简单展示事务控制,此处可以设置事务隔离级别,事务的回滚触发等等
* @see org.springframework.transaction.annotation.Transactional
* @see com.geek.calvin.controller.TransactionController
*/
@Override
@Transactional(rollbackFor=Exception.class)
public void showTransaction() {
//随便拿一个学生,设置年龄
List<Student> students = studentMapper.listAll();
Student student = students.get(0);
student.setAge(100);
studentMapper.updateById(student);
//制造异常,除数不能为0
System.out.println(1/0);
Course course = new Course();
course.setCourseName("Effective Java Ⅱ");
course.setClassify(2);
course.setGrade(3);
course.setLessonsCount(20);
courseMapper.insert(course);
}
}
小结
1:springboot集成mybatis
2:springboot+mybatis周边配置及常用插件
存在问题
1:没有深入探究mybatis事务
2:多数据源配置及分库分表插件未探究
3:没有架如mybatis缓存等设置