Mybatis
HNU Peter Lv2

Mybatis

1、简介

1.1 什么是Mybatis

image-20220502094016850

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>

1.2 持久层

数据持久化

  • 将程序的数据在持久态和瞬时状态转换。(数据库,io)

持久层:

  • 完成持久化工作的代码块

2、Mybatis程序示例

步骤:环境搭建—–>导入依赖—->编码

2.1 搭建环境导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

</dependencies>

2.2 创建子项目/模块

  1. 编写Mybatis核心配置文件

mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>


</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/pans/dao/UserMapper.xml"/> //注意添加dao映射关系
</mappers>
</configuration>
  1. 编写Mybatis工具类(连接数据库)

MybatisUtils

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

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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
// static{
// try {
// String resource = "mybatis-config.xml";
// InputStream inputStream = null;
// inputStream = Resources.getResourceAsStream(resource);
// sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// } catch (IOException e) {
// e.printStackTrace();
// }
//
//
// }
public static SqlSession getSqlSession(){

try {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}


return sqlSessionFactory.openSession();
}
}

  1. Dao接口
1
2
3
4
5
6
7
8
9
10
package com.pans.dao;

import com.pans.pojo.User;

import java.util.List;

public interface UserDao {
public List<User> getUserList();
}

  1. ==接口实现==

由原来的实现类变成xml文件UserMapper.xml

1
2
3
4
5
6
7
8
9
<?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">
<mapper namespace="com.pans.dao.UserDao">
<select id="getUserList" resultType="com.pans.pojo.User">
select * from mybatis.user
</select>
</mapper>

==注意:==

namespace: 表示需要实现的接口类

id: 需要实现的接口类里面的方法

**resultType:**方法的返回类型

parameterType:方法的参数类型

  1. 测试类
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
package com.pans.dao;

import com.pans.pojo.User;
import com.pans.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserDaoTest {
@Test
public void test(){

//第一步:获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();

//执行SQL
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();

for (User suser : userList) {
System.out.println(suser);
}

//关闭sqlSession
sqlSession.close();

}
}

文件结构:

常见问题:

  1. Maven导出资源问题java.io.IOException: Could not find resource mybatis-config.xml

解决办法:pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
  1. 配置文件未注册
  2. 接口绑定错误
  3. 方法名错误
  4. 返回类型错误

3.3 CRUD

需求1:根据用户id查询用户信息

  1. 在Dao(Mapper层)添加相应接口方法
1
2
//根据id查询用户
public User getUserById(int id);
  1. 在xml文件中实现查询语句并绑定接口方法(一个方法对应一个sql标签语句)
1
2
3
<select id="getUserById" parameterType="int" resultType="com.pans.pojo.User">
select * from mybatis.user where id=1;
</select>
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testGetUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserDao mapper = sqlSession.getMapper(UserDao.class);

User user = mapper.getUserById(1);
System.out.println(user);

sqlSession.close();

}

需求2:增加/修改/删除 用户

Dao接口:

1
2
3
4
5
6
7
8
//增加用户
public void addUser(User user);

//修改用户
public void upDateUser(User user);

//删除一个用户
public void deleteUser(int id);

接口对应xml

1
2
3
4
5
6
7
8
9
10
11
12
<!--    增加用户,实体类的属性可以直接用-->
<insert id="addUser" parameterType="com.pans.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<!-- 修改用户-->
<update id="upDateUser" parameterType="com.pans.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
<!-- 删除用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>

==注意:==

  • 当接口参数个数大于1时该如何处理?
  • 注意在增删改时最后必须提交事务

测试类:

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
 @Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserDao mapper = sqlSession.getMapper(UserDao.class);

mapper.addUser(new User(5,"apa","3412"));

sqlSession.commit(); //提交事务
sqlSession.close();
}

@Test
public void testUpdateUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();

UserDao mapper = sqlSession.getMapper(UserDao.class);

mapper.upDateUser(new User(4,"hello","world"));

sqlSession.commit(); //提交事务
sqlSession.close();
}

@Test
public void testDeleteUser(){
SqlSession sqlSession=MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);

mapper.deleteUser(5);

sqlSession.commit(); //提交事务
sqlSession.commit();
}

3.4 Map(多参数)

Dao接口:

1
2
//增加用户(多参数)
public void addUser2(Map map);

xml:

1
2
3
4
<!--    增加用户2(多参数)-->
<insert id="addUser2" parameterType="map" >
insert into user (id,name,pwd) values(#{userId},#{userName},#{userPwd});
</insert>

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testAddUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);

HashMap<Object, Object> map = new HashMap<>();

map.put("userId",6);
map.put("userName","asdasd");
map.put("userPwd","dasdas");

mapper.addUser2(map);

sqlSession.commit();
sqlSession.commit();
}

3.5 模糊查询

Dao接口:

1
2
//模糊查询
public List<User> getUserLike(String subName);

mapper.xml——>1

1
2
3
<select id="getUserLike" parameterType="String" resultType="com.pans.pojo.User">
select * from user where name like %#{value}%
</select>

mapper.xml——->2

1
2
3
<select id="getUserLike" parameterType="String" resultType="com.pans.pojo.User">
select * from user where name like #{value}
</select>

Test—–>1

1
2
3
4
5
6
7
8
9
10
11
 @Test
public void testGetUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> user = mapper.getUserLike("张");
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}

Test—>2

1
2
3
4
5
6
7
8
9
10
11
 @Test
public void testGetUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> user = mapper.getUserLike("张%");
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}

思考:

方式1是在sql里面写通配符

方式2是在查询语句中写通配符

此时两种方式是否存在sql注入问题!

3、配置解析

3.1 属性优化

mybatis-config.xml里面的properties属性可以引入外部配置文件db.properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username=root
password=123456

mybatis-config.xml中的标签必须按照规定顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<configuration>
<!-- 引入外部配置文件,也可增加自定义属性值-->
<properties resource="db.properties">
<property name="username" value="root"/>
</properties>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/pans/dao/UserMapper.xml"/>
</mappers>
</configuration>

3.3 别名优化(不区分大小写)

==Java编译后的字节码文件名不区分大小写,即同一个包下不能有两个仅有大小写区分的类名==

  1. 在mybatis-config.xml可以使用typeAliases便签进行实体类别名优化: 它仅用于 XML 配置,意在降低冗余的全限定类名书写
1
2
3
<typeAliases>
<typeAlias type="com.pans.pojo.User" alias="User"/>
</typeAliases>

对应的mapper.xml

1
2
3
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
  1. 也可以指定一个包名,实体类的每一个类的别名为其首字母小写!(大写也可以)
1
2
3
<typeAliases>
<typeAlias type="com.pans.pojo"/>
</typeAliases>
  1. 也可在实体类中加入注解Alias作为其别名
1
2
3
4
5
6
7
@Alias("user")
public class User {
public User(long id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}

3.4 设置

image-20220504104943796

数据库不区分大小写,故用下划线区分!

3.5 映射器(Mapper)

方式一:

1
2
3
<mappers>
<mapper resource="com/pans/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件注册

1
2
3
4
    <mappers>
<!-- <mapper resource="com/pans/dao/UserMapper.xml"/>-->
<mapper class="com.pans.dao.UserMapper"/>
</mappers>

注意:接口和其配置文件必须同名且在同一个包下!!!

方式三:使用包名

1
2
3
<mappers>
<package name="com.pans.dao"/>
</mappers>

3.6 生命周期和作用域

用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder:

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要(用于局部方法变量);

SqlSessionFactory:(数据库连接池)

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例(用于应用作用域);

可以使用单例模式或静态单例模式。

SqlSession:(数据库请求,用完需要关闭)

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的(请求或方法作用域)

image-20220504113606707

4、属性名和字段名不一致问题

数据库中的字段和实体类中的字段名不一致的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--    结果集映射-->
<resultMap id="UserMap" type="User">
<!-- column数据库中的字段,property实体类的属性-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<!-- 实体字段名和表字段名不一致-->
<result column="pwd" property="password"/>

</resultMap>
<!-- 根据id查询用户-->
<select id="getUserList" resultMap="UserMap">
select id,name,pwd from mybatis.user where id=${id}
</select>

5、日志

  1. 标准日志工厂
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  1. Log4j
  • maven依赖:
1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

  • 增加配置文件log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### set log levels ###
log4j.rootLogger = stdout,D

### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{1}:%L - %m%n

### 输出到日志文件 ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File =./log/pans-info.log
log4j.appender.D.Append = true
log4j.appender.D.MaxFileSize = 10mb
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

  • 测试
1
2
3
4
5
6
7
8
9
@Test
public void testLog4j(){
Logger logger = Logger.getLogger(UserDaoTest.class);

logger.info("进入Log4j=====>info方法!");
logger.debug("进入Log4j=====>debug方法!");
logger.error("进入Log4j=====>error方法!");

}

6、分页

  • mysql里的分页limit
1
select * from user limit startIndex,pageSize;
  • Mybatis分页:

除了修改sql语句,还需使用map

1
2
3
4
<!--    查询用户,分页-->
<select id="getUserByPage" parameterType="map" resultMap="userList">
select * from user limit #{startIndex},#{pageSize};
</select>
1
2
3
Map<String, Integer> map = new HashMap<>();
map.put("startIndex",2);
map.put("pageSize",2);
  • pageHelper插件!!

7、使用注解开发

直接在接口上方使用注解写sql语句,并在xml配置文件mapper属性改为class接口。(自动提交事务)

1
2
@Select("select * from user")
public User getUserList();
1
2
3
//根据id查询用户
@Select("select * from user where id=${id}")
public User getUserById(@Param("id")int id);

关于@param

  • 基本类型为参数或者String类型,需要加上
  • 引用类型可以不用加

8、Lombok

直接在实体类前加@Data可以自动生成get/set/constructer等方法。。。

9、多对一处理

  1. 子查询

  2. 联表查询

1
2
3
SELECT u.`id`,u.`name`,t.`name` 
FROM `user` AS u,`teacher`AS t
WHERE u.`teacher_id`=t.`id`;

StudentMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--接口实现-->
<mapper namespace="com.pans.dao.StudentMapper">
<!-- 联表查询用户及其对应老师信息-->
<select id="getStudentList" resultMap="studentList">
SELECT s.`id` id,s.`name` sname,t.`name` tname
FROM teacher t,student s
WHERE s.tid=t.id;
</select>
<resultMap id="studentList" type="student">
<result property="id" column="id"/>
<result property="name" column="sname"/>

<association property="teacher" javaType="Teacher">

<result property="name" column="tname"/>
</association>

</resultMap>

</mapper>

注意:实体类属性与数据库列并不是一一对应:(即属性名与字段不一致)

1
2
3
4
5
6
7
8
public class Student {

private long id;
private String name;
//数据库中的属性为tI作为Student表的外键
//学生需要关联的老师
private Teacher teacher;
}

10、一对多处理(略)

11、动态SQL

何为动态sql:

==动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式进行组合,而无需在业务层面就行查询。==

  1. 导包
1
2
3
4
5
6
7
8
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

  1. 编写配置文件(注意开启驼峰命名自动转换)
1
2
3
4
5
6
<mappers>
<mapper resource="com/pans/dao/BlogMapper.xml"/>
</mappers>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  1. 编写实体类及其Mapper接口及其实现的xml文件
1
2
3
4
5
6
7
8
9
10

@AllArgsConstructor
@NoArgsConstructor
public class Blog {

private String id;
private String title;
private String author;
private Date createTime; //属性名和字段名不一致,需开启驼峰命名转换
private long views;
1
2
3
4
public interface BlogMapper {
//插入数据
public void addBlog(Blog blog);
}
1
2
3
4
5
6
7
8
<mapper namespace="com.pans.dao.BlogMapper">

<insert id="addBlog" parameterType="blog">
insert into blog(id,title,author,create_time,views)
values(#{id},#{title},#{author},#{createTime},#{views});
</insert>

</mapper>
  1. 编写测试文件
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
import com.pans.dao.BlogMapper;
import com.pans.pojo.Blog;
import com.pans.utils.IDutils;
import com.pans.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;

public class Mytest {
@Test
public void addBlogTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getID());
blog.setTitle("Mybatis");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);

mapper.addBlog(blog);

blog.setId(IDutils.getID());
blog.setTitle("Java");
mapper.addBlog(blog);

blog.setId(IDutils.getID());
blog.setTitle("Spring");
mapper.addBlog(blog);

blog.setId(IDutils.getID());
blog.setTitle("微服务");
mapper.addBlog(blog);
sqlSession.commit();

sqlSession.close();
}
}

1、 if语句

  1. mapper接口
1
2
//动态SQL之IF语句
public List<Blog> queryByIf(Map map);
  1. mapper配置文件
1
2
3
4
5
6
7
8
9
10
11
<!--    动态Sql之IF语句-->
<select id="queryByIf" parameterType="map" resultType="Blog">
select * from blog where 1=1
<if test="title != null">
and title=#{title};
</if>
<if test="author!=null">
and author="狂神说";
</if>

</select>
  1. 测试文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testQueryByIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Map map = new HashMap();
// map.put("title","Java");
map.put("author","dasd");
List<Blog> blogs = mapper.queryByIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}

2、where语句

类似Java中的switch语句,并且where便签能自动识别第一个成立条件并去掉可能存在的and/or。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="queryByWhere" resultType="blog" parameterType="map">
select * from blog
<where>
<choose>
<when test="titile!=null">
title=#{title}
</when>
<when test="author!=null">
author=#{author}
</when>
<otherwise>
views=#{views}
</otherwise>
</choose>
</where>
</select>

3、set语句

set元素会动态前置SET关键字,同时也会删掉无关的逗号

4、foreach

集合遍历

12、缓存

1、一级缓存

一级缓存(本地会话缓存)默认开启,只在一次sqlSession中有效!

查询相同数据的时候直接从缓存中获取,而无需经过数据库查询,提高系统效率。

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 会使用(LRU,FIFO)等算法来刷新缓存。

2、二级缓存

开启方式:加标签<cache/>,又称全局缓存,可跨sqlSession缓存!

3、自定义缓存