什么是MyBatis

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

官方文档:https://mybatis.org/mybatis-3/zh/getting-started.html

持久层

定义:

  • 完成持久化工作的代码块
  • 层的界限十分明显

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc)、io文件持久化

为什么需要持久化

  • 不能丢掉的对象
  • 节约内存

为何选择MyBatis

  • 灵活性:MyBatis允许您直接编写原生的SQL语句,提供了很高的灵活性。您可以根据需要编写任何复杂的SQL,从而满足各种业务需求
  • 易于使用:MyBatis通过XML配置文件和注解,让您可以轻松地将SQL语句与Java代码分离,使得代码结构更清晰、易于维护
  • 映射简单:MyBatis提供了简单的映射方式,可以将数据库表中的字段自动映射到Java对象的属性上,降低了数据转换的复杂性
  • 良好的扩展性:MyBatis提供了丰富的插件接口,您可以通过编写自定义插件来扩展MyBatis的功能,以满足特定需求
  • 与其他框架集成:MyBatis可以与Spring、Spring Boot等流行框架无缝集成,提供更加完整的解决方案
  • 社区支持:MyBatis有一个活跃的开发者社区,为使用者提供了丰富的文档、教程和支持。这有助于在遇到问题时快速找到解决方案

MyBatis框架解决JDBC劣势

针对JDBC编程的劣势,MyBatis提供了以下解决方案,具体如下。

  • 问题一:数据库链接创建、释放频繁会造成系统资源浪费,从而影响系统性能。

解决方案:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

  • 问题二:SQL语句在代码中硬编码,造成代码不易维护。在实际应用的开发中,SQL变化的可能较大。在传统JDBC编程中,SQL变动需要改变Java代码,违反了开闭原则。

解决方案:MyBatis将SQL语句配置在MyBatis的映射文件中,实现了与Java代码的分离。

  • 问题三:使用preparedStatement向占位符传参数存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改SQL需要修改代码,造成系统不易维护。

解决方案:MyBatis自动将Java对象映射至SQL语句,通过Statement中的parameterType定义输入参数的类型。

  • 问题四:JDBC对结果集解析存在硬编码(查询列名),SQL变化导致解析代码变化,造成系统不易维护。

解决方案:MyBatis自动将SQL执行结果映射至Java对象,通过Statement中的resultType定义输出结果的类型。

学习示例(注释版)

新建一个user数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create database `user`
use `user`

create table `user_table`(
`id` int(20) not null auto_increment,
`user_name` varchar(32) not null,
`sex` int(1) default null,
`age` int(20) default null,
`register_time` datetime CURRENT_TIMESTAMP,
`create_time` datetime CURRENT_TIMESTAMP,
`update_time` datetime CURRENT_TIMESTAMP
primary key(`id`)
);
insert into user_table values ('张三','1','29');
insert into user_table values ('李四','0','27');
insert into user_table values ('王五','1','39');

pom.xml文件里面导入MyBatis相关jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>

<!-- jdbc链接容器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

application.yml文件中配置数据库连接信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接池
url: linkurl...
username: username
password: password
# type配置为HikariDataSource,这样就可以使用HikariCP连接池了
type: com.zaxxer.hikari.HikariDataSource
# HikariCP连接池的配置
hikari:
minimum-idle: 5
maximum-pool-size: 15
auto-commit: true
idle-timeout: 30000
pool-name: DatebookHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1

将工程目录按照层划分

  • controller:此目录主要是存放Controllerde ,比如:UserController.java,也有的项目是把action放在controller目录下,有的是把UserController.java放在action目录下。
  • service:这里分接口和实现类,接口在service目录下,接口实现类在service/impl目录下。
  • dao:持久层,目前比较流行的Mybatis或者jpa之类的。

controller下创建实体类

Controller层通常用于处理HTTP请求并充当应用程序的接口入口。

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
56
57
58
package com.test.controller;

import com.test.handler.exception.BodyDecryptException;
import com.test.model.Result;
import com.test.model.User;
import com.test.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

// @RestController注解相当于@ResponseBody + @Controller合在一起的作用。
@RestController
@RequestMapping(value = "/user")
public class UserController extends Result {

@Autowired
private UserService userService;

@RequestMapping(value = "/getUserInfo", method = RequestMethod.GET)
public String getUserInfo(Integer id) {
return userService.getUserInfo(id);
}

@RequestMapping(value = "/getAllUserInfo", method = RequestMethod.GET)
public String getAllUserInfo() {
return userService.getAllUserInfo();
}

@PostMapping(value = "/addUser")
public String addUser(@RequestBody Map<String, String> user) throws BodyDecryptException {
return userService.addUserInfo(user);
}

@PostMapping(value = "/addUser/batchImport")
public String batchImport(@RequestBody List<User> params) {
return userService.batchAddUserInfo(params);
}

// 更新用户信息
@RequestMapping(value = "/updateUser", method = RequestMethod.PUT)
public String updateUser(@RequestBody Map<String, String> user) {
return userService.updateUserInfo(user);
}

// 删除指定用户
@RequestMapping(value = "/deleteUser", method = RequestMethod.DELETE)
public String deleteUser(Integer id) {
return userService.deleteUser(id);
}

// 批量删除用户
@RequestMapping(value = "/deleteUser/batchDelete", method = RequestMethod.DELETE)
public String batchDeleteUser(String[] ids) {
return userService.batchDeleteUser(ids);
}
}

dao下创建UserDao

DAO(数据访问对象)层是用于与数据库进行交互的层。它负责执行数据库操作,包括查询、插入、更新和删除数据。

创建Mapper接口,使用注解方式定义SQL查询和操作方法。这里示例对user_table进行增删改查的方法:

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
56
57
58
59
60
61
62
63
64
package com.test.dao;

import com.test.model.User;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;

@Mapper
public interface UserDao {

@Results(id = "UserResultMap", value = {
@Result(property = "id", column = "id"),
@Result(property = "userName", column = "user_name"),
@Result(property = "sex", column = "sex"),
@Result(property = "age", column = "age"),
@Result(property = "birthday", column = "birthday"),
@Result(property = "registerTime", column = "register_time"),
@Result(property = "createTime", column = "create_time")
})
@Select("select * from user_table where id = #{id}")
User getUserInfoById(Integer id);

// 通过@ResultMap实现@Results共用
@ResultMap("UserResultMap")
@Select("select * from user_table")
List<User> getAllUserInfo();

@Insert("insert into user_table (user_name, sex, age, birthday) values" +
"(#{userName}, #{sex}, #{age}, #{birthday})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
void addUser(Map<String, String> param);

@Insert("<script>" +
"insert into user_table (user_name, sex, age, birthday) values " +
"<foreach collection='list' item='item' index='index' separator=','>" +
"(#{item.userName}, #{item.sex}, #{item.age}, #{item.birthday})" +
"</foreach>" +
"</script>")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
void batchAddUser(@Param("list") List<User> paramList);

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

// 根据id批量删除用户
@Delete("<script>" +
"delete from user_table where id in " +
"<foreach collection='array' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
void batchDeleteUser(String[] idList);

@Update("<script>" +
"update user_table set " +
"user_name = #{userName}, " +
"sex = #{sex}, " +
"<if test = 'age != null'> age = #{age}, </if> " +
"birthday = #{birthday} " +
"where id = #{id}" +
"</script>")
void updateUser(Map<String, String> userInfo);
}

在启动类下面配置MyBatis

在Spring Boot配置类上添加 @MapperScan 注解来扫描Mapper接口的包。

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
package com.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootApplication
@EnableAsync
@EnableScheduling
// @MapperScan注解会扫描指定包下的所有接口,然后生成这些接口的实现类
@MapperScan("com.test.dao")
public class AiSenEnvApplication {

public static void main(String[] args) {
SpringApplication.run(AiSenEnvApplication.class, args);
}

@Bean(name = "executorPool")
public ExecutorService executorPool() {
int curSystemThreads = Runtime.getRuntime().availableProcessors();
System.out.println("线程个数:" + curSystemThreads);
ExecutorService pool = Executors.newFixedThreadPool(curSystemThreads);
return pool;
}
}

启动应用程序

调用controller层接口测试数据的增删改查

addUser.png

addUser_mq.png

学习示例(使用XML配置来执行基本的CRUD操作)

配置数据库连接信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接池
url: linkurl...
username: username
password: password
# type配置为HikariDataSource,这样就可以使用HikariCP连接池了
type: com.zaxxer.hikari.HikariDataSource
# HikariCP连接池的配置
hikari:
minimum-idle: 5
maximum-pool-size: 15
auto-commit: true
idle-timeout: 30000
pool-name: DatebookHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1

配置MyBatis扫描项目mapper的目录

application.yml文件中进行如下配置:

1
2
3
4
5
mybatis:
# 搜索指定包别名
# typeAliasesPackage: com.test.model
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath:mapper/*.xml

创建MyBatis映射文件

src/main/resources/mapper下创建UserManageMapper.xml,在这里将通过XML定义SQL语句

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?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">
<!-- 1. namespace: 指定命名空间,命名空间的值必须是一个合法的Java类名,这个类名就是Mapper接口的全限定名 -->
<mapper namespace="com.test.dao.UserDao">
<resultMap id="UserResultMap" type="com.test.model.User">
<result property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="registerTime" column="register_time"/>
<result property="createTime" column="create_time"/>
</resultMap>

<!-- 2. select: 指定一个查询语句,id属性指定这个查询语句的唯一标识,parameterType属性指定这个查询语句的参数类型,resultMap属性指定这个查询语句的结果集映射 -->
<select id="getAllUserInfo" resultMap="UserResultMap">
select *
from user_table
</select>

<!-- 3. insert: 指定一个插入语句,id属性指定这个插入语句的唯一标识,parameterType属性指定这个插入语句的参数类型,useGeneratedKeys属性指定是否使用自动生成的主键,keyProperty属性指定自动生成的主键的属性名,keyColumn属性指定自动生成的主键的列名 -->
<insert id="addUser" parameterType="java.util.Map">
insert into user_table (user_name, sex, age, birthday)
VALUES (#{userName}, #{sex}, #{age}, #{birthday})
</insert>

<insert id="batchAddUser" parameterType="java.util.List">
insert into user_table (user_name, sex, age, birthday) VALUES
<!-- collection: 指定一个集合映射,property属性指定这个集合映射的属性名,javaType属性指定这个集合映射的类型,column属性指定这个集合映射的列名,select属性指定这个集合映射的查询语句 -->
<foreach collection="list" item="item" index="index" separator=",">
(#{item.userName}, #{item.sex}, #{item.age}, #{item.birthday})
</foreach>
</insert>

<!-- 4. update: 指定一个更新语句,id属性指定这个更新语句的唯一标识,parameterType属性指定这个更新语句的参数类型 -->
<update id="updateUser">
<!-- set: 指定一个SQL片段,如果这个SQL片段不为空,则在这个SQL片段前添加set关键字 -->
update user_table set
<!-- if: 指定一个条件判断,test属性指定这个条件判断的条件,如果条件成立,则执行其中的SQL语句 -->
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
age = #{age},
<if test="birthday != null">
birthday = #{birthday}
</if>
<!-- where: 指定一个SQL片段,如果这个SQL片段不为空,则在这个SQL片段前添加where关键字 -->
where id = #{id}
</update>

<!-- 5. delete: 指定一个删除语句,id属性指定这个删除语句的唯一标识,parameterType属性指定这个删除语句的参数类型 -->
<delete id="deleteUser" parameterType="integer">
delete
from user_table
where id = #{id}
</delete>

<delete id="batchDeleteUser">
delete
from user_table
where id in
<foreach collection="array" item="id" index="index" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

dao下创建UserDao

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
package com.test.dao;

import com.test.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

@Mapper
public interface UserDao {

User getUserInfoById(Integer id);

List<User> getAllUserInfo();

void addUser(Map<String, String> param);

void batchAddUser(@Param("list") List<User> paramList);

void deleteUser(Integer id);

void batchDeleteUser(String[] idList);

void updateUser(Map<String, String> userInfo);
}

测试

使用api工具测试CRUD,全部通过

mybatis_xml.png