Mybatis
Mybatis
基础环境搭建
-
是什么
mybatis
是一个 sql
映射框架, 提供的数据库的操作能力, 增强的 JDBC
,使用mybatis
让开发人员集中精神写 sql
就行了, 不必关心 Connection、Statement、ResuletSet
的创建, 销毁, sql
的执行
-
新建表
1
2
3
4
5
6
7
8
9
10
11
12-- 创建数据库
create database springdb CHARACTER SET utf8 COLLATE utf8_general_ci;
-- 使用数据库
use springdb;
-- 创建表
create table Student
(
userId int not null primary key comment '用户ID',
userName varchar(80) not null comment '用户名',
email varchar(20) not null comment '邮箱',
userAge int default 18
) character set utf8; -
加入
maven
依赖坐标, mysql
驱动坐标 1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency> -
创建实体类,
表 -
创建持久层的
dao
接口, 定义操作数据库的方法 -
创建一个
mybatis
使用的配置文件 该配置文件
叫做sql
映射文件: 写 sql
语句的。一般一个表对应一个 sql
映射文件 - 文件类型为
xxx.xml
- 文件创建在
dao
所在接口目录中 - 文件名和
接口保持一致
-
创建
mybatis
的主配置文件 - 一个项目就一个
主配置文件
主配置
文件提供了数据库的连接信息和sql
映射文件的位置信息
- 一个项目就一个
-
创建使用
mybatis
类 - 通过
mybatis
访问数据库
- 通过
-
jar
下载 1
https://mvnrepository.com/artifact/org.mybatis/mybatis
mybatis
下载
-
映射文件解释
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
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="" resultType="">
</select>
</mapper>
<!--
sql映射文件: 写 sql 语句的,mybatis 会执行这些 sql
1. 指定约束文件
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
mybatis-3-mapper.dtd 是约束文件的名称,扩展名是 dtd
2. 约束文件作用: 限制和检查在当前文件中出现的标签,属性必须符合`mybatis`要求
3. mapper 是当前文件的根标签,必须的
namespace: 叫做命名空间,唯一值的, 可以是自定义的字符串, 要求你使用 dao 接口的全限定名称
4. 在当前文件中,可以使用特定的标签, 表示数据库的特定操作
: 表示更新数据库的操作, 就是在 标签中 写 update sql 语句
: 表示插入, 放的是 insert 语句
: 表示删除, 执行的是 delete 语句
<select id="" resultType=""> </select>
id: 你要执行的 sql 语句的唯一标识,mybatis会使用这个 id 值来找到要执行的 sql 语句, 可以自定义, 但是要求你使用接口中的方法名称
resultType: 表示结果类型,是 sql 语句执行后得到 ResultSet, 遍历这个 ResultSet 得到 Java 对象的类型. 值写的类型的全限定名称
-->
-
主配置文件详解
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
<configuration>
<!--
环境配置: 数据库的连接信息
default: 必须和某个 environment 的 id 值一样
告诉 mybatis 使用那个数据库的连接信息,也就是访问那个数据库
-->
<environments default="development">
<!--
environment: 一个数据库信息的配置,环境
id: 一个唯一的值,可以自定义, 表示环境的名称
-->
<environment id="development">
<!--
transactionManager: mybatis 的事物类型
type: JDBC(表示使用 jdbc中的 Connection 对象的 commit,rollback 做事务处理)
-->
<transactionManager type="JDBC"/>
<!--
dataSource: 表示数据源,连接数据库的
type: 表述数据源的类型, POOLED 表示使用连接池
-->
<dataSource type="POOLED">
<!--
driver、user、username、password 是固定的,不能自定义
-->
<property name="driver" value="${driver}"/>
<!-- 连接数据库的 url 字符串 : value="jdbc:mysql://localhost:3306/springdb" -->
<property name="url" value="${url}"/>
<!-- 访问数据库的用户名:value="root" -->
<property name="username" value="${username}"/>
<!-- 密码: value="root" -->
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
-
如果项目为手动创建,
注意设置文件目录类型 | 设置目录类型 |
| :———————————————————-: |
| |
-
配置
pom.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<build>
<resources>
<resource>
<!-- 所在目录 -->
<directory>src/main/java</directory>
<includes>
<!-- 包括目录下的 下的 properties,xml 过滤 -->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<!-- resource 下的 properties,xml 过滤 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build> -
注意使用的
namespac、id、resultSet
等的值信息 -
mybatis
主配置文件中关于 url
连接的参数问题等 1
2
3
4
55.1+:
<property name="url" value="jdbc:mysql://localhost:3306/springdbautoReconnect=true&useSSL=false"/>
8.0+:
<property name="url" value="jdbc:mysql://localhost:3306/springdbuseUnicode=true&serverTimezone=GMT&useSSL=false"/> -
如果运行过程中文件位置不能正确解析,
则可以进行项目重新构建、清理 IDEA
缓存或执行 mvn clean,mvn compile
等进行解决
-
sql
映射文件 1
2
3
4
5
6
7
8
9
10
<mapper namespace="com.coderitl.dao.StudentDao">
<select id="selectStudents" resultType="com.coderitl.domain.Student">
select userId, userName, email, userAge from Student
</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
<configuration>
<settings>
<!-- 设置 mybatis 输出日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<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/springdb?autoReconnect=true&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件路径 -->
<mappers>
<mapper resource="com/coderitl/dao/StudentDao.xml"/>
</mappers>
</configuration> -
entity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mysql> use springdb;
Database changed
mysql> desc student;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| userId | int(11) | NO | PRI | NULL | |
| userName | varchar(80) | NO | | NULL | |
| email | varchar(20) | NO | | NULL | |
| userAge | int(11) | YES | | 18 | |
+----------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
public class Student {
private Integer userId;
private String userName;
private String email;
private Integer userAge;
// 需提供为 JavaBean...
} -
dao
接口 1
2
3
4
5
6
7
8package com.coderitl.dao;
import java.util.List;
public interface StudentDao {
public List<Student> selectStudents();
} -
测试
mybatis
连接, 注意包资源问题
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
38package com.coderitl.mybatis;
import com.coderitl.domain.Student;
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;
import java.util.List;
public class DoMainAppFirst {
public static void main(String[] args) throws IOException {
// TODO: 访问 mybatis 读取student 数据
// 1.定义 mybatis 主配置文件名称, 从类路径的根路径开始 (target/class)
String config = "mybatis.xml";
// 2. 读取这个 config 表示的文件
InputStream in = Resources.getResourceAsStream(config);
// 3. 创建 SqlSessionFactoryBuilder 对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 4. 创建SqlSessionFactory 对象
SqlSessionFactory factory = builder.build(in);
// 5. [重要] 获取SqlSession 对象, 从 SqlSessionFactory 中获取 sqlSession
SqlSession sqlSession = factory.openSession();
// 6. [重要] 指定要执行的 sql 语句的标识,sql 映射文件中的 namespace+"."+标签的 id 值
String sqlId = "com.coderitl.dao.StudentDao" + "." + "selectStudents";
// 7. 执行sql 语句, 通过 sqlId 找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
// 8. 输出结果
studentList.forEach(student -> System.out.println(student));
// 9. 关闭 sqlSession 对象
sqlSession.close();
}
} -
查询结果
结果映射
主要类的介绍
-
主要类
Resources
是 mybatis
中的一个类, 负责读取主配置文件 1
InputStream in = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactoryBuilder
: 创建SqlSessionFactory
对象 1
2
3
4SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 创建
SqlSessionFactory factory = builder.build(in);SqlSessionFactory
重量级对象,程序创建一个对象耗时比较长,使用资源比较多, 在整个项目中, 有一个就够用了 1
2
3
4
5
6
7
8
9SqlSessionFactory: 是接口,
接口实现类为 DefaultSqlSessionFactory
SqlSessionFactory 作用: 获取 SqlSession 对象。
SqlSession sqlSession = factory.openSession();
openSession()方法说明:
1. openSession(): 无参数的,获取是非自动提交事物的 SqlSession 对象
2. openSession(bollean autoCimmit):
openSession(true): 获取自动提交事物的 SqlSession 对象
openSession(false): 非自动提交事物的 SqlSession 对象SqlSession
接口: 定义了操作数据的方法,例如 selectOne()、selectList()、insert()、update()、delete()、commit()、rollback()
SqlSession
接口的实现类 DefaultSqlSession
使用要求:
SqlSession
对象不是线程安全的, 执行 SqlSession.close()
这样能保证它的使用是线程安全的 -
封装工具类
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
36package com.coderitl.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 factory = null;
static {
// 项目的 mybatis 主配置文件
try {
String config = "mybatis.xml";
InputStream in = Resources.getResourceAsStream(config);
// 创建SqlSessionFactory,使用 SqlSessionFactoryBuild
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取SqlSession 的方法
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
if (factory != null) {
// 自动提交事物
sqlSession = factory.openSession(true);
}
return sqlSession;
}
} -
使用工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 测试工具类使用
public void testMybatisUtils() {
// 获取 SqlSession 对象,从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 指定要执行的 sql 语句标识,sql 映射文件 namespace+"."+标签 id 的值
// 6. [重要] 指定要执行的 sql 语句的标识,sql 映射文件中的 namespace+"."+标签的 id 值
String sqlId = "com.coderitl.dao.StudentDao" + "." + "selectStudents";
// 7. 执行sql 语句, 通过 sqlId 找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
// 8. 输出结果
studentList.forEach(student -> System.out.println(student));
// 9. 关闭 sqlSession 对象
sqlSession.close();
} -
输出
使用 mybatis 工具类
-
动态代理和参数
-
动态代理
List<Student> studentList = dao.selectStudents();
-
dao
对象,类型是 studentDao
,全限定名称是:com.coderitl.dao.StudentDao
全限定名称 和namespace
是一样的 -
方法名称:
selectStudents
(传统dao
使用 => 接口和实现类),这个方法就是 mapper
文件中的id
值selectStudents
-
通过
dao
中方法的返回值也可以确定Mybatis
要调用的是SqlSession
的方法
如果返回值是List
,调用的是 SqlSession.selectList()
方法
如果返回值是int
或者是非
List 的, 看 mapper
文件中的标签是insert
、update
就会调用SqlSession
的insert
、update
等方法
-
-
是什么?
mybatis
根据 dao
的方法调用, 获取执行 sql
的语句信息, mybatis
根据你的 dao
接口, 创建出一个 dao
接口的实现类, 并创建这个类的对象, 完成 SqlSession
调用方法, 访问数据库 -
实现
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
46package com.coderitl.mybatis;
import com.coderitl.dao.StudentDao;
import com.coderitl.entity.Student;
import com.coderitl.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;
import java.util.List;
public class TestMybatis {
public void testProxySelect() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
// 调用 dao 方法 执行数据库的操作
List<Student> students = dao.selectStudents();
for (Student stu : students) {
System.out.println(stu);
}
sqlSession.close();
}
public void testProxyInsert() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
// 调用 dao 方法 执行数据库的操作
Student student = new Student(1003, "day02", "day02@qq.com", 2);
int res = dao.insertStudent(student);
if (res > 0) {
System.out.println("数据添加成功!");
} else {
System.out.println("数据添加失败");
}
sqlSession.close();
}
} -
输出
动态代理实现结果输出
-
测试
1
2
3
4
5
6
7
8
9
10interface:
/**
* 一个简单类型的参数
* 简单类型: mybatis 把 java 的基本数据类型和string 都叫简单类型
*
* 在 mapper 文件中获取简单类型的一个参数的值,使用 #{任意字符}
* @param userId
* @return
*/
public Student selectStudentById(Integer userId);1
2
3
4
5
6
<select id="selectStudentById" parameterType="java.lang.Integer" resultType="com.coderitl.entity.Student">
select *
from Student
where userId = #{userId}
</select>1
2
3
4
5
6
7
8
public void testSelectStudentById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = mapper.selectStudentById(1001);
System.out.println(student);
sqlSession.close();
}parameterType
:dao
接口中方法参数的数据类型 parameterType
它的值是 Java
的数据类型全限定名称或者是 mybatis
定义的别名 1
2parameterType="java.lang.Integer"
parameterType="int"注意:
parameterType
不是强制的, mybatis
通过反射机制能够发现接口参数的数据类型, 所以可以没有,
一般不写 -
使用
#{}
之后, mybatis
执行 sql
是使用的 jdbc
中的 PrepareStatement
对象 1
2
3String sql = "select * from Studnet where id=?";
PrepareStatement pst = conn.prepareStatement(sql);
pst.setInt(1,1003); -
执行
sql
封装为 resultType=”com.coderitl.eneity.Student“
对象1
2
3
4
5
6
7
8
9
10
11
12ResultSet rs = ps.executeQuery();
Student student = null;
while(rs.next()){
// 从数据库中取表的一行数据,存放到一个 Java 对象属性中
student = new Student();
student.setUserId(rs.getUserId("userId"));
...
}
rerurn student; // 给了 dao 方法调用的返回值
=> Student student = mapper.selectStudentById(1001);日志分析
-
-
映射文件
1
2
3
4
5
6
7<!-- 自定义参数名称使用 -->
<select id="selectMuliParam" resultType="com.coderitl.entity.Student">
select *
from Student
where userName = #{myname}
or userAge = #{myage}
</select> -
接口
1
2
3
4
5
6
7
8
9/**
* 多参数: 命名参数,在形参的前面加入 @Param("自定义参数名称")
*
* @param userName
* @param userAge
* @return
*/
List<Student> selectMuliParam(; String userName, Integer userAge) -
测试
1
2
3
4
5
6
7
8
9
10
public void testSelectMuliParam() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
List<Student> students = mapper.selectMuliParam("coder-itl", 1001);
for (Student stu : students) {
System.out.println(stu);
}
sqlSession.close();
} -
输出
SelectMuliParam
-
定义对象
1
2
3
4
5public class QueryParam {
private String paramName;
private Integer paramAge;
// JavaBean...
} -
定义接口
1
2
3
4
5
6
7/**
* 多个参数: 使用 java 对象作为接口中方法的参数
*
* @param param
* @return
*/
List<Student> selectMuliObject(QueryParam param); -
映射文件
sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--
多个参数: 使用 java 对象的属性值,作为参数的的实际值
使用对象语法: #{属性名,javaType=类型名称 (java.lang.String),jdbcType=varchar} 该方法不常用
javaType: 指的是 java 中的属性的数据类型
jdbcType: 指的是 在数据库中的数据类型
常用: #{属性名} javaType、jdbcType 的值 mybatis 反射能获取
-->
<select id="selectMuliObject" resultType="com.coderitl.entity.Student">
select *
from Student
where userName = #{paramName}
or userAge = #{paramAge}
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
public void testSelectMuliObject() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
QueryParam param = new QueryParam("coder-itl", 4);
List<Student> students = mapper.selectMuliObject(param);
for (Student stu : students) {
System.out.println(stu);
}
sqlSession.close();
} -
输出
对象参数使用
-
#
占位符告诉
mybatis
使用实际的参数值代替, 并使用 PrepareStatement
对象执行 sql
语句, #{}
代替 sql
语句的 ?
这样做更安全,通常也是首选方法 -
$
字符串替换 告诉
mybatis
使用 $
包含的 字符串
替换所在位置.使用 Statement
把 sql
欲绝的 ${}
的内容连接起来,主要是用在替换表名、列名、不同列排序等操作
-
复现
-
接口
1
2
3
4public Student selectStudentById(Integer userId);
public Student selectStudentById_$( Integer userId); -
映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#(占位符)
<select id="selectStudentById" parameterType="java.lang.Integer" resultType="com.coderitl.entity.Student">
select *
from Student
where userId = #{userId}
</select>
$(字符串拼接):
<select id="selectStudentById_$" parameterType="java.lang.Integer" resultType="com.coderitl.entity.Student">
select *
from Student
where userId = ${userId}
</select> -
测试
1
2
3
4
5
6
7
8
9
10#:
public void testSelectStudentById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
// ==> Preparing: select * from Student where userId = ?
Student student = mapper.selectStudentById(1001);
System.out.println(student);
sqlSession.close();
}1
2
3
4
5
6
7
8
9
10
11$:
public void testSelectStudentById_$() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
// Preparing: select * from Student where userId = 1001
Student student = mapper.selectStudentById_$(1001);
System.out.println(student);
sqlSession.close();
} -
输出
1
2
3#: Preparing: select * from Student where userId = ? # 占位符
$: Preparing: select * from Student where userId = 1001 # 字符串替换
-
Mybatis 获取参数值的各种情况
-
mapper
接口方法的参数为单个的字面量类型 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!--
可以通过 ${} 和 #{} 以任意的名称获取参数值,但是需要注意 ${} 的单引号问题
-->
<select id="getUserByUserName" resultType="com.coderitl.domain.User" parameterType="string">
select *
from user
where userName = #{username}
</select>
<select id="getUserByUserName" resultType="com.coderitl.domain.User" parameterType="string">
select *
from user
where userName = '${username}'
</select>#{}
${} => 单引号包裹
-
mapper
接口方法的参数为多个时 1
2
3
4
5
6
7
8
9
10
11
12
13
14<!--
此时 mybatis 会将这些参数放在一个 map 集合中,以两种方式进行存储
1. 以 arg0,arg1 为键,以参数为值
2. 以 param1,param2 为键,以参数为值
因此只需要通过 #{} 和 ${} 以键的方式访问值即可,但是需要注意 ${} 的单引号问题
-->
<!-- User checkLoginByMap(Map<String,Object> map); -->
<select id="checkLoginByMap" resultType="user">
select *
from user
where userName = #{username}
and password = #{password}
</select>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 测试 map 参数
public void testArgsByMap() {
// 获取 SqlSession 对象,从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("username","root");
map.put("password","root");
// 调用接口方法
User user = mapper.checkLoginByMap(map);
System.out.println("user = " + user);
}map
参数 -
mapper
接口方法的参数是 实体类类型
1
2
3
4<!-- int insertUser(User user); 通过访问实体类属性值即可 -->
<insert id="insertUser" parameterType="user">
insert into user (userName, password) values (#{userName}, #{password});
</insert>1
2
3
4
5
6
7
8
9
10// 测试 实体类 参数
public void testArgsByEntity() {
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("entity", "entity");
// 调用接口方法
int userInfo = mapper.insertUser(user);
System.out.println("user = " + userInfo);
}实体类参数 -
命名参数
1
2
3
4
5
6
7<!-- User checkLoginByParam(@Param("userName") String username,@Param("password") String password); -->
<select id="checkLoginByParam" resultType="user">
select *
from user
where userName = #{userName}
and password = #{password}
</select>@Param("userName")
Mybatis 的各种非查询功能
-
查询一个实体类
1
2
3
4
5
6
7
8
9
10
11
12<!--
1. 若查询的数据只有一条,可以通过实体类对象或者集合接受
2. 若查询出的结果数据有多条,可以通过 List 集合接受, 一定不能通过实体类对象接受, 此时会抛出异常 TooManyResultException
dao 接口的数据类型,而不是 resultType
-->
<!-- User getUserById(@Param("id") int id); -->
<select id="getUserById" resultType="user">
select * from user where id=#{id}
</select> -
单行单列查询
1
2
3
4
5<!-- Integer getUserCount() -->
<select id="getUserCount" resultType="integer">
select count(*)
from user
</select> -
Map*
1
2
3
4
5
6
7
8
9<!-- 根据用户 id 查询用户信息为一个 map 集合 -->
<!-- Map<String, Object> getUserByIdToMap(@Param("id") int id) -->
<select id="getUserByIdToMap" resultType="map">
select *
from user
where id = #{id}
</select>1
2
3
4
5
6
7
public void testGetUserByIdToMap() {
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 调用接口方法
System.out.println(mapper.getUserByIdToMap(1));
}返回结果为 Map
-
所有数据转换为
Map
1
2
3
4
5<!-- List<Map<String, Object>> getAllUserToMap(); -->
<select id="getAllUserToMap" resultType="map">
select *
from user
</select>1
2
3
4
5
6
7
8
public void testGetAllUserByIdToMap() {
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Map<String, Object>> allUserToMap = mapper.getAllUserToMap();
System.out.println("allUserToMap = " + allUserToMap);
}List<Map<String, Object>> getAllUserToMap();
1
2
3
4
5// 如果不是用 List
Map<String, Object> getAllUserToMap();@MapKey(key)
特殊的 SQL 执行
-
根据用户名模糊查询用户信息
1
2
3
4
5
6
7<!-- List<User> getUserByLike(@Param("username") String username); -->
<select id="getUserByLike" resultType="user">
select *
from user
where userName like '%${username}%'
</select>'%${username}%'
resultType
不匹配出现的异常 1
2
3
4
5
6
<select id="getUserByLike" resultType="user">
select *
from user
where userName like concat('%', #{username}, '%')
</select>concat('%', #{username}, '%')
1
2
3
4
5
6
<select id="getUserByLike" resultType="user">
select *
from user
where userName like "%"#{username}"%"
</select>单引号匹配的结果非正常内容信息 双引号为正常解析内容 -
批量删除
1
2
3
4
5
6<!-- int deleteAll(String ids); -->
<delete id="deleteAll">
delete
from user
where id in (${ids})
</delete>1
2
3
4
5
6
7
public void testDeleteAll() {
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int deleteAll = mapper.deleteAll("6,7,8");
System.out.println("deleteAll = " + deleteAll);
}SQL 条件只能使用 ${ }
,因为 #{}
会添加 单引号
-
动态设置表名
(只能使用 ${ }
)1
2
3
4
5<!-- List<User> getUserByTableName(@Param("tableName") String tableName); -->
<select id="getUserByTableName" resultType="user">
select *
from ${tableName}
</select> -
添加功能获取自增的主键
1
2
3
4
5
6
7
8
9
10<!--
void insertUserInfo(User user); 添加之后,获取自增主键
useGeneratedKeys="true" 表示标签中的 SQL 使用了自增主键
keyProperty="id" 获取的属性放在了传输过来的对象的 id 上
-->
<insert id="insertUserInfo" useGeneratedKeys="true" keyProperty="id">
insert into user (userName, password)
values (#{userName}, #{password});
</insert>1
2
3
4
5
6
7
8
9
public void testInsertUserInfo() {
// 获取接口的字节码文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("user", "user");
// 输出信息包含主键
mapper.insertUserInfo(user);
System.out.println(user);
}输出 主键
自定义映射ResultMap
-
ResultMap
处理字段和属性的映射关系 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20-- <!-- 若字段名和实体类中的属性名不一致,则可以通过 ResultMap 设置自定义映射 -->
create table t_emp
(
eid int not null primary key auto_increment comment '员工id',
emp_name varchar(20),
age int,
sex char,
email varchar(20)
);
-- 添加字段 did 关联
alter table t_emp add column did int;
-- 添加测试数据
insert into t_emp(emp_name, age, sex, email, did) values ('root1',18,'男','123@qq.com',1);
insert into t_emp(emp_name, age, sex, email, did) values ('root2',28,'男','123@qq.com',2);
insert into t_emp(emp_name, age, sex, email, did) values ('root3',38,'男','123@qq.com',3);
insert into t_emp(emp_name, age, sex, email, did) values ('root4',48,'男','123@qq.com',1);
insert into t_emp(emp_name, age, sex, email, did) values ('root5',58,'男','123@qq.com',2);1
2
3
4
5
6
7
8
9
10
11
12-- 部门表
create table t_dept
(
did int not null primary key auto_increment,
dept_name varchar(20)
);
-- 测试数据
insert into t_dept(dept_name) values('A');
insert into t_dept(dept_name) values('B');
insert into t_dept(dept_name) values('C'); -
解决
mysql
属性与 实体类
属性不一致问题1
// TODO: mysql: emp_name entity: empName
-
解决方法一
起别名
1
2
3
4
5
<select id="getAllEmp" resultType="emp">
select *, emp_name as empName
from t_emp
</select> -
全局配置
下划线自动映射为驼峰
1
2
3
4
5
<settings>
<!-- 将下划线自动映射为驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings> -
方式三:
ResultMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!--
id="empResultMap" => 唯一标识
type="emp" => 要处理的实体类
-->
<resultMap id="empResultMap" type="emp">
<!-- 主键的映射关系 -->
<id property="eid" column="eid"/>
<!--
使用了 resultMap 尽可能设置所有属性、无论匹配与否
property: 实体类对应的属性名
column: 数据库对应的属性名
-->
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<!-- List<Emp> getAllEmp(); -->
<select id="getAllEmp" resultType="emp" resultMap="empResultMap">
select *
from t_emp
</select>1
2
3
4
5
6<resultMap>
<!-- 处理多对一的映射关系 -->
<association property=""></association>
<!-- 处理一多对的映射关系 -->
<collection property=""></collection>
</resultMap>ResultMap
-
-
多对一映射 => 多的实体类里面创建一的对象 ->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Emp implements Serializable {
private int eid;
private String empName;
private int age;
private String sex;
private String email;
private int did;
// 多的实体类里面创建一的对象
private Dept dept;
}-
方式一
级联映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<resultMap id="getEmpAndDeptResultMap" type="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<!-- dept: 级联映射 -->
<result property="dept.did" column="did"/>
<result property="dept.deptName" column="dept_name"/>
</resultMap>
<select id="getEmpAndDept" resultMap="getEmpAndDeptResultMap">
select *
from t_emp
left join t_dept te on t_emp.eid = te.did
where eid = #{eid};
</select>1
2
3
4
5
6
7
8
9/**
* 查询员工信息以及对应的部门信息
*/
public void testGetEmpAndDept() {
// 方式一: 级联映射解决
Emp empAndDept = mapper.getEmpAndDept(1);
System.out.println("empAndDept = " + empAndDept);
}级联属性赋值 -
方式二
association
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<!--
association: 处理多对一的映射关系
<association property="dept" javaType="Dept">
property="dept" 需要处理多对一的映射关系的映射名 => private Dept dept;
javaType="Dept" 该属性的类型(实体类名)
-->
<resultMap id="getEmpAndDeptResultMap" type="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<!-- dept: association 处理多对一映射 -->
<association property="dept" javaType="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="getEmpAndDeptResultMap">
select *
from t_emp
left join t_dept te on t_emp.eid = te.did
where eid = #{eid};
</select> -
方式三
分步查询 *
推荐 * 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<!-- EmpMapper.xml -->
<!--
<association property="dept"
column="did"
select="com.coderitl.mapper.DeptMapper.getEmpAndDeptByStepTwo"/>
property="dept": 需要处理多对一的映射关系的映射名 => private Dept dept;
column="did": 设置分布查询的条件
select="com.coderitl.mapper.DeptMapper.getEmpAndDeptByStepTwo": 设置分步查询的 sql 的唯一标识(namespace.sqlId)
-->
<resultMap id="getEmpAndDeptByStepOneResultMap" type="emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<association property="dept"
column="did"
select="com.coderitl.mapper.DeptMapper.getEmpAndDeptByStepTwo"/>
</resultMap>
<!-- 通过分步查询员工以及员工所对应的部门信息: Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid); -->
<select id="getEmpAndDeptByStepOne" resultType="emp" resultMap="getEmpAndDeptByStepOneResultMap">
select *
from t_emp
where eid = #{eid}
</select>1
2
3
4
5
6<!-- Dept getEmpAndDeptByStepTwo(@Param("did") Integer did); -->
<select id="getEmpAndDeptByStepTwo" resultType="dept">
select *
from t_dept
where did = #{did}
</select>1
2
3
4
5
public void testGetEmpAndDeptByStepOne_Two() {
Emp empAndDeptByStepOne = mapper.getEmpAndDeptByStepOne(1);
System.out.println("empAndDeptByStepOne = " + empAndDeptByStepOne);
}分步查询
-
推荐原因
-
分布查询的优点
可以实现延迟加载,
但是必须在核心配置文件中设置全局配置信息 lazyLoadingEnabled
: 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载 ( 需要开启。两个默认都是 false
)aggressiveLazyLoading
: 当开启时,任何方法的调用都会加载对象的所有属性, 否则, 每个属性会按需加载 ( 需要关闭
)此时就可以实现按需加载,
获取的数据是什么, 就会执行相应的 sql
,此时可以通过 association
和 collection
中的 fetchType
属性设置当前的分布查询是否使用延迟加载, fetchType
=lazy(延迟加载)
|eage(立即加载)
1
2
3
4
5
6
7
8
9
10
11<!-- 主配置文件内配置 -->
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<association property="dept"
column="did"
fetchType="eager"
select="com.coderitl.mapper.DeptMapper.getEmpAndDeptByStepTwo"/>延迟加载的效果
-
-
-
一对多
的映射关系-
collection
1
2
3
4
5
6
7
8
9
10
11
12
public class Dept implements Serializable {
private int did;
private String deptName;
private List<Emp> emps;
}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<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<collection property="emps" ofType="emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</collection>
</resultMap>
<!-- Dept getDeptAndEmp(@Param("did") Integer did); -->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
-- 获取部门以及部门中所有的员工信息
select te.eid,
te.emp_name,
te.age,
te.sex,
te.email,
te.did,
dept_name
from t_dept
left join t_emp te on t_dept.did = te.did
where te.did = #{did};
</select>collection
处理一对多映射 -
通过分布查询实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14<resultMap id="getDeptAndEmpStepOneResultMap" type="dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<collection property="emps"
column="did"
select="com.coderitl.mapper.EmpMapper.getDeptAndEmpByStepTwo"/>
</resultMap>
<!-- Dept getDeptAndEmpStepOne(@Param("did") Integer did); -->
<select id="getDeptAndEmpStepOne" resultMap="getDeptAndEmpStepOneResultMap">
select *
from t_dept
where did = #{did}
</select>1
2
3
4
5
6<!-- Emp getDeptAndEmpByStepTwo(@Param("did") Integer did); -->
<select id="getDeptAndEmpByStepTwo" resultType="emp">
select *
from t_emp
where eid = #{did}
</select>分步查询
-
动态 SQL
-
注意
mybatis
主配置文件中关于 url
的连接参数中需要添加 characterEncoding=UTF-8
,否则中文内容无法查出结果信息,后续数据库配置中会列出完整配置 注意 实体符号使用 Eg: > is >
-
接口
1
public List<Student> selectLike(; String userName)
-
映射文件
1
2
3
4
5<select id="selectLike" resultType="com.coderitl.entity.Student">
select *
from Student
where userName like #{userName}
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void testSelectLike() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
// 调用 dao 方法 执行数据库的操作
String userName = "%张 %";
List<Student> students = dao.selectLike(userName);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
} -
输出
url
参数影响查询结果
-
动态
sql
同一个
dao
的方法, 根据不同的条件可以表示不同的 sql
语句, 主要是 where
部分的变化 -
语法
test:
使用对象的属性值作为条件 -
接口
1
public List<Student> selectIf(Student student);
-
映射
1
2
3
4
5
6
7
8
9<select id="selectIf" resultType="com.coderitl.entity.Student">
select *
from Student
where
<if test="userName!=null and userName!='' ">
userName = #{userName}
</if>
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void testSelectIf() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
// 调用 dao 方法 执行数据库的操作
Student student = new Student();
student.setUserName("coder-itl");
List<Student> students = dao.selectIf(student);
for (Student stu : students) {
System.out.println(stu);
}
sqlSession.close();
}
-
语法
1
2
3<where>
... 可以有多个 if
</where> -
映射
1
2
3
4
5
6
7
8
9<select id="selectWhere" resultType="com.coderitl.entity.Student">
select *
from Student
<where>
<if test="userName!=null and userName!='' ">
userName = #{userName}
</if>
</where>
</select>
-
语法
分析 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<foreach collection="集合类型"
open="开始的字符"
close="结束的字符"
item="集合中的成员"
separator="集合成员之间的分隔符">
#{item 的值}
</foreach>
<!--
collection: 表示循环的对象是数组,还是 list 集合. 如果 dao 接口方法的形参是数组,collection="array",如果 dao 接口形参是 List,collection="list"
open: 循环开始时的字符 sql.append("(")
close: 循环结束时的字符 sql.append(")")
item: 集合成员,自定义的变量 Interger item = listInfo.get(i) // item 是集合成员
separator: 集合成员之间的分隔符 sql.append(".")
#{item 的值}: 获取集合成员的值
--> -
接口
1
public List<Student> selectForeach1(List<Integer> listId);
-
映射
1
2
3
4
5
6
7
8<select id="selectForeach1" resultType="com.coderitl.entity.Student">
select *
from Student
where userId in
<foreach collection="list" open="(" close=")" separator="," item="myId">
#{myId}
</foreach>
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void testSelectForeach1() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Integer> listInfo = new ArrayList<>();
// 实现 => (1001,1004)
listInfo.add(1001);
listInfo.add(1004);
List<Student> students = dao.selectForeach1(listInfo);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
} -
输出
小括号的内容封装在 list 里面
-
接口
1
public List<Student> selectForeach2(List<Student> studentList);
-
映射
1
2
3
4
5
6
7
8
9
10
11
12
13<!--
循环的 List<Student>
-->
<select id="selectForeach2" resultType="com.coderitl.entity.Student">
select *
from Student
<if test="list !=null and list.size>0">
where userId in
<foreach collection="list" open="(" close=")" separator="," item="stu">
#{stu.userId}
</foreach>
</if>
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void testSelectForeach2() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> list = new ArrayList<>();
Student student1 = new Student();
student1.setUserId(1001);
Student student2 = new Student();
student2.setUserId(1004);
list.add(student1);
list.add(student2);
List<Student> stu = dao.selectForeach2(list);
stu.forEach(student -> System.out.println(student));
sqlSession.close();
}
-
是什么?
sql
标签是一段代码, 可以是 表名,
,几个字段 where
条件都可以, 可以在其他地方复用的 sql
标签内容 -
接口
1
public List<Student> testSql();
-
映射文件
1
2
3
4
5
6
7
8
9
10
11// 定义
<sql id="studentFileList">
userId,userName,email,userAge
</sql>
// 使用
<select id="testSql" resultType="stu">
select
<include refid="studentFileList"/>
from Student
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void testSql() {
/**
* 使用 mybatis 的动态代理机制,使用 SqlSession.getMapper(dao 接口)
* getMapper 能获取 dao 接口对于的实现类对象
*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao dao = sqlSession.getMapper(StudentDao.class);
List<Student> students = dao.testSql();
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
} -
注意
idea 会报错关于 sql
,标签 按下快捷键 alt + Enter
找到 sql|select|insert|update|delete
删除 sql
即可 (解决之后不能复现错误, 只记得大概) -
输出
sql
标签
数据库配置
-
mybatis
主配置文件添加日志配置 ( 方式一: 配置简单
)1
2
3
4
5<!-- settings: 控制 mybatis 全局行为 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings> -
外部文件配置
方式二: 内容更加清晰
-
添加
maven
依赖 1
2
3
4
5<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency> -
在
resources
目录下, 新建文件 log4j.properties
-
填充以下内容
1
2
3
4
5
6
7
8# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
-
1 |
<!-- |
需要把数据库的配置信息放到一个单独文件中,
独立管理, 这个文件扩展名是 properties
,在这个文件中,使用自定义的key=value
的格式表示数据
-
使用步骤
-
在
resources
目录中, 创建 xxx.properties
-
在文件中,
使用 key=value
的格式定义数据 1
2
3
4
5
6jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/database-name?autoReconnect=true&useSSL=false&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root -
在
mybatis
主配置文件, 使用 property
标签引用外部的属性配置文件 1
2
3
4
5
6
7<!--
使用外部属性配置文件
resource: 指定类路径下的某个属性配置文件
注意书写位置:
-->
<properties resource="jdbc.properties"/> -
在使用值的位置,
使用 ${key}
获取对应的 value
(等号右侧的值)1
2
3
4
5
6
7
8
9
10
11
12<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url"
value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
-
-
配置方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!--
第一种方式: resources="mapper文件的路径"
优点: 文件清晰,加载文件是明确的 文件的位置比较灵活
缺点: 文件比较多,代码量会比较大, 管理难度大
-->
<mapper resource="com/coderitl/dao/StudentDao.xml" />
<!--
第二种方式: package"(推荐)
name: mapper文件所在的包名 (如果在 resources 路径下, 需要创建出与 dao|mapper 接口所在包路径一致, 创建方式: resources: Eg: com/coderitl/,apper)
特点: 把这个包中所有的 mapper 文件,一次加载
-->
<package name="com.coderitl.dao" />
<!-- 注解配置 -->
<mapper class="" />package
细节 -
注解使用时机
1
当 SQL 语句比较简单的时候,使用注解绑定就可以了,当 SQL 语句比较复杂的话,使用 xml 方式绑定,一般用 xml 方式绑定比较多 -
MyBatis
接口绑定的几种方式 - 使用注解,在接口的方法上面添加
@Select
@Update
等注解,里面写上对应的 SQL
语句进行 SQL
语句的绑定。 - 通过映射文件
xml
方式进行绑定,指定 xml
映射文件中的 namespace
对应的接口的全路径名
- 使用注解,在接口的方法上面添加
-
主配置文件
完整配置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
<configuration>
<!--
使用外部属性配置文件
resource: 指定类路径下的某个属性配置文件
-->
<properties resource="jdbc.properties"/>
<settings>
<!-- 设置 mybatis 输出日期 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 别名 -->
<typeAliases>
<package name=""/>
</typeAliases>
<!-- 连接 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url"
value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 两种方式 选择其一 -->
<mappers>
<package name="com.coderitl.dao" />
</mappers>
</configuration> -
映射文件
1
2
3
4
5
6
7
8
9
<mapper namespace="">
</mapper>
分页PageHelper
-
分页
PageHelper
做数据分页,在你的 select
语句后面加入分页的sql
内容, 如果使用的是 mysql
数据库, select * from student
后面加入limit
语句
-
加入
pagehelper
依赖 1
2
3
4
5
6<!-- PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency> -
在
mybatis
主配置文件, 加入 plugin
声明 1
2
3
4
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins> -
在
select
语句之前, 调用 PageHelper.startPage(页码,
每页大小)
-
数据准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16MySQL [springdb]> select * from Student;
+--------+-----------+-----------------+---------+
| userId | userName | email | userAge |
+--------+-----------+-----------------+---------+
| 1001 | coder-itl | coderitl@qq.com | 18 |
| 1002 | mybatis | mybatis@qq.com | 4 |
| 1003 | day02 | day02@qq.com | 2 |
| 1004 | day02 | day02@qq.com | 2 |
| 1005 | 张三 | zhangsan@qq.com | 12 |
| 1006 | 1006 | 1006@qq.com | 12 |
| 1007 | 1007 | 1007@qq.com | 12 |
| 1008 | 1008 | 1008@qq.com | 12 |
| 1009 | 1009 | 1009@qq.com | 12 |
| 1010 | 1010 | 1010@qq.com | 12 |
| 1011 | 1011 | 1011@qq.com | 12 |
+--------+-----------+-----------------+---------+ -
接口
1
2// PageHelper
public List<Student> selectLimitPage(); -
映射
1
2
3
4
5<!-- 使用 PageHelper -->
<select id="selectLimitPage" resultType="stu">
select *
from Student
</select> -
测试
1
2
3
4
5
6
7
8
9
10
11
public void testPageHelper() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
// 调用 PageHelper 的方法
PageHelper.startPage(2, 3);
List<Student> students = mapper.selectLimitPage();
students.forEach(stu -> System.out.println(stu));
sqlSession.close();
} -
输出
PageHelper
缓存
-
缓存验证
缓存验证 -
一级缓存
一级缓存也叫做
sqlSession
级别缓存, 为每个 sqlSession
单独分配的缓存内存, 无需动手开启可直接使用, 多个 sqlSession
的缓存时不共享的 -
特性
-
如果多次查询使用的是同一个
sqlSession
对象, 则第一次查询后数据会存放到缓存, 后续的查询则直接访问缓存中存储的数据 同一个 sqlSession
对象 -
如果第一次查询完成后,
对查询出的对象进行修改 ( 此修改会影响到缓存
),第二次查询会直接访问缓存, 造成第二次查询的结果与数据库据不一致 -
当我们进行在查询时想要跳过缓存直接查询数据库,
则可以通过 sqlSession.clearCache()
,来清除当前sqlSession
缓存 -
如果第一次查询之后第二次查询之前,
使用当前的 sqlSession
执行了修改操作, 此修改操作会使第一次查询并缓存的数据失效, 因此第二次查询会再次访问数据库
-
-
一级缓存失效的情况
- 不同的
SqlSession
对应不同的一级缓存 - 同一个
SqlSession
但是查询条件不同 - 同一个
SqlSession
两次查询期间执行了任何一次增删改操作 - 同一个
SqlSession
两次查询期间手动清空了缓存
- 不同的
-
-
二级缓存
二级缓存是
SqlSessionFacaory
级别的,通过同一个 SqlSessionFactory
创建的 SqlSession
查询的结果会被缓存,此后若再次执行相同的查询语句, 结果会从缓存中获取 -
二级缓存开启的条件
-
在核心配置文件中,
设置全局配置属性 cacheEnabled=true
,默认为true
,不需要设置 -
在映射文件中设置标签
<cache/>
设置标签 <cache/>
-
二级缓存必须在
SqlSession
关闭或提交之后有效 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// 缓存命中率 0.0
Cache Hit Ratio [com.coderitl.mapper.EmpMapper]: 0.0
// SqlSession 提交
public void testCacheTwo() {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
Emp empbyEid1 = mapper1.getEmpbyEid(1);
System.out.println("empbyEid1 = " + empbyEid1);
sqlSession1.commit();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
Emp empbyEid2 = mapper1.getEmpbyEid(1);
System.out.println("empbyEid2 = " + empbyEid2);
sqlSession2.commit();
} catch (IOException e) {
throw new RuntimeException(e);
}| 未生效:
必须在 关闭或提交之后有效 SqlSession
|
| :———————————————————-: | :———————————————————-: |
||
|
-
查询的数据转换的实体类类型必须实现序列化接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Emp implements Serializable {
private int eid;
private String empName;
private int age;
private String sex;
private String email;
private int did;
private Dept dept;
}
-
-
二级缓存失效的情况
两次查询之间执行了任意的增删改,会使一级二级缓存同时失效
-
-
二级缓存的相关配置
在
mapper
配置文件中添加的 cache
标签可以设置一些属性 -
eviction
属性: 缓存回收策略 LRU
: 最近最少使用: 移除最长时间不被使用的对象
-
FIFO
: 先进先出: 按对象进入缓存的顺序来移除他们SOFT
: 软引用: 移除基于垃圾回收器状态和软引用规则的对象
-
WEAK
: 弱引用: 更积极地移除基于垃圾回收器状态和弱引用规则的对象 -
flushInterval
:刷新间隔,单位毫秒 默认情况下不设置,
也就是没有刷新间隔, 缓存仅仅调用语句时刷新 -
size
:引用数目,正整数 代表缓存最多可以存储多少个对象,
太大容易导致内存溢出 -
readOnly
:只读:true/false
-
true
:只读缓存,会给所有调用者返回缓存对象的相同实例, 因此这些对象不能被修改, 这提供了很重要的性能优势 false
: 读写缓存,会返回缓存对象的拷贝 ( 通过序列化
),这会慢一些, 但是安全, 因此默认为 false
-
-
Mybatis
缓存查询的顺序 - 先查询二级缓存,
因为二级缓存中可能会有其他程序已经查出来数据, 可以拿来直接使用 - 如果二级缓存没有命中,
在查询一级缓存 - 如果一级缓存也没有命中,
则查询数据库 SqlSession
关闭之后, 一级缓存中的数据会写入二级缓存
- 先查询二级缓存,
整合第三方缓存
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<!-- slf4j 日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency> -
创建配置文件
(固定名称 ehcache.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
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<!-- resources/ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="E:\mybatis-review\ehcache"/>
<!--
defaultCache:默认缓存策略,当ehcache 找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout 将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认是 0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是 30MB。每个 Cache 都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120 秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。你可以设置为 FIFO(先进先出)或是 LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache> -
设置二级缓存类型
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
添加
logback.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
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
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/home"/>
<!--控制台日志, 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!--文件日志, 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>
<!-- myibatis log configure-->
<logger name="com.apache.ibatis" level="DEBUG"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.coderitl.mapper" level="DEBUG"/>
</configuration>
Mybatis 的逆向工程
-
正向工程: 先创建
Java
实体类, 由框架负责根据实体类生成数据表, hibernate
支持正向工程 -
逆向工程: 先创建数据库表,
由框架负责根据数据库表, 反向生成如下资源 Java
实体类 Mapper
接口 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<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build> -
创建逆向工程的配置文件
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<!-- generatorConfig.xml 固定名称 -->
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程
MyBatis3: 生成带条件的 CRUD
MyBatis3Simple: 生成基本的 CRUD
-->
<context id="context" targetRuntime="MyBatis3">
<property name="javaFileEncoding" value="UTF-8"/>
<!-- suppressAllComments 设置为 true 则不再生成注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 数据库的相关配置 -->
<jdbcConnection
driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/score_system?autoReconnect=true&useSSL=false&characterEncoding=UTF-8"
userId="root"
password="root"/>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 实体类生成的策略 -->
<javaModelGenerator targetPackage="com.coderitl.domain" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- Mapper.xml 文件的生成策略 -->
<sqlMapGenerator targetPackage="com.coderitl.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- Mapper 接口文件的生成策略 -->
<javaClientGenerator targetPackage="com.coderitl.mapper" targetProject="src/main/java" type="XMLMAPPER">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--
table指定每个生成表的生成策略 表名 和 model 实体类名
tableName="*" 可以对应所有表,此时不写 domainObjectName
domainObjectName 属性指定生成出来的实体类类名
-->
<table tableName="t_emp" domainObjectName="Emp"></table>
<table tableName="t_dept" domainObjectName="Dept"></table>
</context>
</generatorConfiguration> -
执行
执行如下插件
-