[[JDBC链接&关闭]]
[[spring]]

MyBatis

什么是MyBatis

MyBatis是一款优秀的持久层框架,用于简化JDBC开发

持久层

  • 负责将数据保存到数据库的那一层代码
  • javaEE三层架构:表现层,业务层,持久层

框架

  • 半成品软件,是一套可重用的,通用的,软件基础代码模型
  • 在框架的基础之上构建软件编写更加高校,规范,通用,可扩展

快速入门

  1. 创建user表,添加数据 –> 对应pojo类
  2. 创建模块,导入坐标
  3. 编写mybatis核心配置文件 –> 替换链接信息,解决硬编码问题
  4. 编写SQL映射文件 –> 统一管理sql语句,解决硬编码问题
  5. 编码
    1. 定义pojo类
    2. 加载核心配置文件,获取SqlSessionFactory对象
    3. 获取SqlSession对象,执行SQL语句
    4. 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mybatis-config

<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bookta"/>
<property name="username" value="root"/>
<property name="password" value="csh20011103"/>
</dataSource>
</environment>
</environments>
<mappers>
添加映射
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UserMapper

<?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">

namespace为空间,名字随便写,之后访问的时候用name.语句id即可
<mapper namespace="name">

id为唯一值,resultType对应返回的类型
<select id="selectAll" resultType="org.example.pojo.User">
select * from bookta;
</select>
</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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
该文件对应用户数据,位于pojo文件夹下

package org.example.pojo;

public class User {
private String isbn;
private String title;
private String type;
private double price;

public String getIsbn() {
return isbn;
}

public void setIsbn(String isbn) {
this.isbn = isbn;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "User{" +
"isbn='" + isbn + '\'' +
", title='" + title + '\'' +
", type='" + type + '\'' +
", price=" + price +
'}';
}
}

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
测试用例

package org.example;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.example.pojo.User;
import org.example.service.BookService;
import org.example.service.impl.BookServiceImpl;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
//1. 加载mybatis的核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();

//3. 执行sql
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);

//4. 释放资源
sqlSession.close();
}
}

解决sql报错

  • 产生原因:idea和数据库没有建立链接,不识别表信息
  • 解决方法:在idea中配置mysql数据库链接

Mapper代理开发

1
2
3
4
5
6
7
8
9
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();

//3. 执行sql
List<User> users = sqlSession.selectList("test.selectAll");

更换写法
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();

Mapper代理步骤

  1. 定义与SQL映射文件同名的Mapper接口,并将Mapper接口和SQL映射文件放置在同一目录
  2. 设置SQL映射文件的namespace属性为Mapper接口全限定名
  3. 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
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
第一步
在resource文件夹下建立新的文件夹,与mapper文件夹格式一样,再把UserMapper拖入该包中
注意如果不是软件包需要'/'来分格,而不是'.'
例如:新建文件夹名:"org.exmple.mapper"

第二步
namespace值改为接口全限定名
例如:
<mapper namespace="org.example.mapper.UserMapper">
<select id="selectAll" resultType="org.example.pojo.User">
select * from bookta;
</select>
</mapper>

第三步
在接口中定义方法
public interface UserMapper {
User selectAll();
}

如果返回是个集合则要改为List:
public interface UserMapper {
List<User> selectAll();
}

第四步
更改配置文件中映射路径
<mappers>
<mapper resource="org/example/mapper/UserMapper.xml"/>
</mappers>

tip:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载:

1
2
3
<mappers>
<package name="org.example.mapper"/>
</mappers>

其他配置名

typeAliases -> 别名

在配置文件中直接添加typeAlisaes可以直接扫包,这样在UserMapper返回类型中可以直接使用类型名字

扫包是最简单的方法

1
2
3
4
5
6
7
8
9
10
11
mybatis-config:
<typeAliases>
<package name="com.itheima.mapper"/>
</typeAliases>

UserMapper:
<mapper namespace="org.example.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from bookta;
</select>
</mapper>

注意:书写时需要遵循先后顺序!

增删改查

配置文件完成增删改查

查找

数据库表的字段名称和实体类的属性名称不一样,则不能自动封装数据

方法一:

起别名:对不一样的列名起别名,让别名和实体类的属性名一样(不建议)

可以引入sql片段解决

1
2
3
4
5
<sql id="test">
...
</sql>

select <include refid="test" />

方法二:

resultMap解决

1
2
3
4
5
6
7
8
9
<resultMap id="名字(标识)" type="类型名">
里面有id和result两个属性,其中id用来对主键进行映射,result进行对一般字段的映射
<result column="列名称(数据库中)" property="属性名(自己pojo类中)"/>
</resultMap>

把resultType替换成resultMap
<select id="selectAll" resultMap="上面的id名称">
select * from bookta;
</select>
占位符

#{key}或者${key}

#{}会替换为?,防止sql注入

${}会拼sql,会存在sql注入问题

表名或者列名不固定的时候可以使用${}

1
2
3
4
<select id="selectAll" resultType="返回类型">
select * from bookta where id = #{id};
</select>
parameterType不是很重要,会自动按类型装配
1
2
接口
(类型) selectById(int id);
特殊字符处理
  1. 转义字符:小于号(<) -> &lt;
    多用于特殊字符少的时候
  2. CDATA区 -> 输入CD回车 -> 出现CDATA区,在内部写入即可
    多用于特殊字符多的时候
多条件查询

模糊匹配 SQL参数 like %需要查询数据%

  1. 散装参数:@Param(“SQL占位符名称”)

    1
    @Param("SQL占位符名称")类型 变量名
  2. 如果有对象可以直接传对象

  3. 传递map,需要key对应

动态条件查询

SQL语句会随着用户的输入或外部条件的变化而变化,我们称为动态SQL

mybatis提供许多标签:if

if & where
1
2
3
4
<if test="逻辑表达式">
SQL语句
</if>
这里面字符串的不等于不能用equals方法,应该用!=,&&应换成and

该语句可能会出现问题,比如sql语句最终多个and

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
第一条不成立时会出错
where
<if test="逻辑表达式">
SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>

解决方法一:
where 1 = 1
<if test="逻辑表达式">
and SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>

解决方法二:使用where标签来替换where关键字
<where>
<if test="逻辑表达式">
and SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>
<if test="逻辑表达式">
and SQL语句
</if>
</where>
choose(when, otherwise)

类似于switch

1
2
3
4
5
6
7
8
9
10
11
12
13
<choose>
<when test="逻辑表达式">
SQL语句
</when>
<when test="逻辑表达式">
SQL语句
</when>
<otherwise>
SQL语句
</otherwise>
</choose>

可以使用where标签进行优化,如果没有条件的话where标签不会生成where

添加

事务默认为关闭,所以会导致添加不上去,需要手动提交事务

1
2
3
执行完命令之后使用
sqlSession.commit();
进行手动提交事务

可以在获取SqlSeiion对象的时候设置自动提交事务

1
2
SqlSession sqlSession = sqlSessionFactory.openSession(true);
如果为false则需要手动提交事务
添加,主键返回

设置属性keyProperty=”主键名”即可拿出id的值,同时需要设置userGeneratedKeys=’true’

1
2
3
4
<insert id="insertTest" useGeneratedKeys="true" keyProperty="id">
insert into table_name (name)
VALUES (#{name});
</insert>

修改

修改全部字段
1
2
3
4
5
<update id="updateTest">
update table_name
set name = #{name}
where id = #{id};
</update>
修改动态字段

使用<set>用法和<where>一样

1
2
3
4
5
6
7
public interface TestMapper {
返回值类型可以为int,如果为int则返回的是修改的行数
int updateTest(Test test);

不返回行数
void updateTest(Test test);
}

删除

1
2
3
4
5
<delete id="deleteTest">
delete
from table_name
where id = #{id};
</delete>

批量删除

编写接口方法

1
void deleteByIds(@Param('ids') int[] ids);

编写sql语句

  • collection:遍历的数组名
  • item:属性
  • separator:分隔符号
  • open和close:开头和结尾
1
2
3
4
5
6
7
8
<delete id="deleteTests">
delete
from table_name
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

如果不用@Param,那么底下的collection的值改为”array”

其他

MyBatis中多个参数时会封装为Map集合

1
2
void deleteByIds(@Param('user') String user1);
-> Map['user'] = user1

注解完成增删改查

一般用户简单功能

  • 查询:**@Select(sql语句)**
  • 添加:**@Insert(sql语句)**
  • 修改:**@Update(sql语句)**
  • 删除:**@Delete(sql语句)**

MyBatis-plus

配置文件

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.2.0</version>
</dependency>

使用

需要继承BaseMapper

1
2
3
@Mapper
public interface UserDao extends BaseMapper<User>{
}

打印sql

需要在配置文件中加上

1
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

复杂Sql

对查询用QueryWrapper,对修改用UpdateWrapper
需要先构造一个QueryWrapper<pojo>,再对其规则进行规定,例如:

1
2
3
4
5
void selectStaff() {  
QueryWrapper<Staff> qw = new QueryWrapper<>();
qw.likeRight("tel","6");
System.out.println(staffService.list(qw));
}

注意:不支持以及不赞成在 RPC 调用(远程过程调用)中把 Wrapper 进行传输(以下为官方解释)

  1. wrapper 很重
  2. 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
  3. 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
  4. 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr

主键自动填充

id要想自动填充,需要在pojo类中添加注解@TableId

1
2
3
4
5
6
7
8
9
10
@Data  
public class Staff {
private String name;
private String tel;
private String account;
private String password;
private Boolean manager;
@TableId(value = "id", type = IdType.ID_WORKER)
private Long id;
}

其中type有多种选择

这里注意如果id是整型,那么数据库中id应为bigint
java中id为Long类型
因为自动生成是自动生成19位,会超出int范围

代码生成器

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
72
package com.manpower.generator;  

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.SQLException;
import java.util.Collections;

public class CodeGenerator {
public static void main(String[] args) throws SQLException {

// <1> 数据库连接信息 ============================================== String url = "jdbc:mysql://127.0.0.1:3306/manpower";
String username = "root";
String password = "csh20011103";

FastAutoGenerator.create(url, username, password)
// 1.全局配置
.globalConfig(builder -> {
builder
.author("haog") // 设置作者
// .enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
// <2> 代码生成位置 ============================================== .outputDir("F:\\editor\\project\\manpower\\src\\main\\java"); // 指定输出目录
})

// 2.包配置(PackageConfig)
.packageConfig(builder -> {
// <3> 包名、项目名、mapper路径 ============================================== builder.parent("com.manpower") // 设置父包名
.moduleName("") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "F:\\editor\\project\\manpower\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
})

// 3.模板配置(TemplateConfig)
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板

// 4.注入配置(InjectionConfig)
// .injectionConfig(
// builder -> {
// builder.customMap(Collections.singletonMap("test", "baomidou"));
// }
// )

// 5.策略配置(StrategyConfig)
.strategyConfig(builder -> {
// <4> 生成的表名 ====================================================== builder.addInclude("staff") // 设置需要生成的表名

// 5.1 Entity 策略配置
.entityBuilder()
.enableLombok() //开启 lombok 模型
.enableTableFieldAnnotation() //开启生成实体时生成字段注解
.naming(NamingStrategy.underline_to_camel) //数据库表映射到实体的命名策略
.columnNaming(NamingStrategy.underline_to_camel) //数据库表字段映射到实体的命名策略

// 5.2 Controller 策略配置

.controllerBuilder()
.enableRestStyle() //开启生成@RestController 控制器

// 5.3 Service 策略配置
.serviceBuilder()

// 5.4 Mapper 策略配置
.mapperBuilder()
.enableBaseResultMap(); //启用 BaseResultMap 生成
// .enableBaseColumnList(); //启用 BaseColumnList
})
.execute();

}
}

所需要的jar包

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
<dependency>  
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!-- mybatis-plus分页插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.0</version>
</dependency>
<!--这两个主要的依赖要加上-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>

注意!需要在mapper中手动加上**@Mapper**,实体类中要加上toString方法,否则会返回值会为地址
官方service使用文档:
CRUD 接口 | MyBatis-Plus (baomidou.com)