MyBatis
1. 环境
- JDK 1.8
- Maven 3.6.1
- IDEA
- MySQL 5.7
中文文档:mybatis – MyBatis 3
2. 简介
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- 通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
3. 获取MyBatis
-
maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency>
-
GitHub
4. 持久层→数据持久化
持久化:将程序的数据在持久状态和瞬时状态转化的过程
- 完成持久化工作的代码块
- 层界限明显
5. 第一个MyBatis程序
5.1 创建数据库
create database mybatis;
use mybatis;
drop table if exists user;
create table if not exists user(
id int(20) not null primary key auto_increment,
name varchar(30) default null,
pwd varchar(30) default null
)auto_increment=1001 engine=InnoDB default charset=utf8;
insert into user(name,pwd) values
('张三','123456'),('李四','6543221'),('王五','1234567');
5.2 新建项目
新建普通Maven项目
删除src目录
导入依赖
<dependencies>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
5.3 创建模块
-
编写MyBatis核心配置文件
<?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="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <!--当xml文件在resources文件夹下时,输入路径时应将‘/’改为'\'--> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
-
编写MyBatis工具类
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; //SqlSessionFactory --> sqlSeesion public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { //使用MyBatis获取sqlSessionFactory对象 String resource= "mybatis-config.xml"; try { InputStream inputStream= Resources.getResourceAsStream(resource); sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } //获得 SqlSession 的实例 //通过 SqlSession 实例来直接执行已映射的 SQL 语 } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
5.4 编写代码
-
实体类
public class User { private int id; private String name; private String pwd; public User() { } public User(String name, String pwd) { this.name = name; this.pwd = pwd; } public int getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
-
dao接口
import com.wang.pojo.User; import java.util.List; public interface UserDao { List<User> getUserList(); }
-
接口实现类
由原来的UserDaoImpl转变为Mapper配置文件
<?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会绑定对应Dao/Mapper接口--> <mapper namespace="org.example.dao.UserDao"> <select id="getUserList" resultType="org.example.pojo.User"> select * from user </select> </mapper>
5.5 测试
绑定异常:org.apache.ibatis.binding.BindingException
核心配置文件中缺少mapper.xml文件的注册
<mappers> <mapper resource="org/example/dao/UserMapper.xml"/> </mappers>
在pom文件中配置resources
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
测试类 UserDaoTest.java
package org.example.dao;
import org.apache.ibatis.session.SqlSession;
import org.example.pojo.User;
import org.example.utils.MyBatisUtils;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
运行结果
6. CRUD(增删改查)
6.1 namespace
namespace中的包名要和Dao/mapper中的包名一致
6.2 select
选择、查询语句
- id:对应namespace中的方法名
- resultType:Sql语句执行的返回值
- parameterType:输入的参数类型
<select id="getUserById" parameterType="int" resultType="org.example.pojo.User">
select * from user where id=#{id}
</select>
@Test
public void getUserByIdTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(1001);
System.out.println(userById);
sqlSession.close();
}
6.3 insert
<!--对象中的属性可以直接取出-->
<insert id="addUser" parameterType="org.example.pojo.User">
insert into user(name,pwd) value (#{name},#{pwd});
</insert>
@Test
public void addUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int num=mapper.addUser(new User("张伟","12345678"));
if(num>0)
System.out.println("插入成功!");
sqlSession.commit();//提交事务
sqlSession.close();
}
6.4 update
<update id="updateUser" parameterType="org.example.pojo.User">
update user
set pwd = #{pwd}
where name=#{name};
</update>
@Test
public void updateUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int num=mapper.updateUser(new User("张伟","1234234"));
if(num>0)
System.out.println("修改成功!");
sqlSession.commit();
sqlSession.close();
}
6.5 delete
<delete id="deleteUser" parameterType="org.example.pojo.User">
delete
from user
where name=#{name};
</delete>
@Test
public void deleteUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(new User("张伟","12345678"));
sqlSession.commit();
sqlSession.close();
}
7. Map
当实体类或数据库中的表、字段或参数过多时,应考虑使用Map
<insert id="addUser2" parameterType="map">
insert into user (name,pwd)
values (#{userName},#{password});
</insert>
@Test
public void addUser2(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("userName","张伟");
map.put("password","11112222");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
Map传递参数,直接在sql中取出key【parameterType=“map”】
对象传递参数,直接在sql中取对象的属性 【parameterType=“Object”】
8. 模糊查询
-
java代码执行的时候,传递通配符%
List<User> userList = mapper.getUserLike("张%");
-
在sql拼接中使用统配符
select * from user where name like "%"#{value}"%";
9. 配置解析
9.1 核心配置文件
- mybatis-config.xml
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
9.2 环境配置(environments)
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
每个SQLSessionFactory实例只能选择一种环境
MyBatis默认事务管理器:JDBC,默认连接池POOLED
9.3 属性(properties)
通过properties属性可以实现引用配置文件
- 属性可以在外部进行配置,并可以进行动态替换
- 既可以在**典型的 Java 属性文件(db.properties)**中配置这些属性,也可以在 properties 元素的子元素中设置
编写配置文件db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=ture&characterEncoding=UTF-8
username=#{username}
password=#{password}
在核心配置文件中引入
<properties resource="db.properties"/>
- 可以直接引入外部文件
- 可以在其中增加属性配置
- 优先使用外部配置文件
9.4 类型别名(typeAliases)
仅用于 XML 配置,意在降低冗余的全限定类名书写
<typeAliases>
<typeAlias alias="User" type="org.example.pojo.User"/>
</typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
<typeAliases>
<package name="org.example.pojo.User"/>
</typeAliases>
默认别名为这个类的类名,首字母小写,如果有注解,则别名为注解值
9.5 设置(Settings)
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
9.6 映射器(mappers)
方式一
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
接口和mapper文件必须同名,且在同一包下
方式三
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
9.7 其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis plus
- 通用mapper
10. 生命周期和作用域
作用域和生命周期错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
- 创建SqlSessionFactory,成功后便不再需要
- 最佳作用域是方法作用域(局部变量)
SqlSessionFactory
- 相当于数据库连接池,一旦被创建就应当一直存在。
- 最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接连接池的请求
- 使用完之后需要立刻关闭,避免占用资源
- 最佳的作用域是请求或方法作用域
解决属性名和字段名不一致的问题
数据库中的字段
User类
public class User {
private int id;
private String name;
private String password;
}
Test类
@Test
public void getUserLikeTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1001);
System.out.println(user);
sqlSession.close();
}
测试结果
解决方法
-
别名
<select id="getUserById" parameterType="int" resultType="org.example.pojo.User"> select id,name,pwd as password from user where id=#{id} </select>
-
resultMap(结果集映射)
<!--结果集映射--> <resultMap id="UserMap" type="org.example.pojo.User"> <!--column 数据库中的字段 property 实体类中的属性--> <result column="pwd" property="password"/> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserMap"> select * from user where id=#{id} </select>
11.日志
11.1 日志工厂
当数据库操作出现异常,需要排错时,需要使用日志
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|
可以在设置中设置在MyBatis中具体使用的日志
默认为 STDOUT_LOGGING 标准日志输出
在MyBatis核心配置文件中配置日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
11.2 LOG4J2
导入依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
在resources文件夹下建立log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--
日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
All:最低等级的,用于打开所有日志记录.
Trace:是追踪,就是程序推进一下.
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
Info:消息在粗粒度级别上突出强调应用程序的运行过程.
Warn:输出警告及warn以下级别的日志.
Error:输出错误信息日志.
Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
OFF:最高等级的,用于关闭所有日志记录.
-->
<!--
status="warn" 日志框架本身的输出日志级别,可以修改为debug
monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5秒;生产环境中修改配置文件,是热更新,无需重启应用
-->
<configuration status="info" monitorInterval="5">
<!--
集中配置属性进行管理
使用时通过:${name}
-->
<properties>
<!--日志所在的文件夹-->
<property name="LOG_HOME">D:\软件数据\IDEA_java\MyBatis\log</property>
</properties>
<!-- 日志处理 -->
<Appenders>
<!-- 控制台输出 appender,SYSTEM_OUT输出黑色,SYSTEM_ERR输出红色 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
</Console>
<!-- 日志文件输出 appender -->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</File>
<!-- 使用随机读写流的日志文件输出 appender,性能提高 -->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</RandomAccessFile>
<!-- 按照一定规则拆分的日志文件的appender --> <!-- 拆分后的文件 -->
<!-- filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log"> -->
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd}-%i.log">
<!-- 日志级别过滤器 -->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<!-- 日志消息格式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
<Policies>
<!-- 在系统启动时,出发拆分规则,生产一个新的日志文件 -->
<OnStartupTriggeringPolicy />
<!-- 按照文件大小拆分,10MB -->
<SizeBasedTriggeringPolicy size="2MB" />
<!-- 按照时间节点拆分,规则根据filePattern定义的 -->
<TimeBasedTriggeringPolicy />
</Policies>
<!-- 在同一个目录下,文件的个限定为 30个,超过进行覆盖 -->
<DefaultRolloverStrategy max="10" />
</RollingFile>
</Appenders>
<!-- logger 定义 -->
<Loggers>
<!-- 使用 rootLogger 配置 日志级别 level="trace" -->
<Root level="trace">
<!-- 指定日志使用的处理器 -->
<!-- <AppenderRef ref="Console" />-->
<AppenderRef ref="file" />
<AppenderRef ref="rollingFile" />
<AppenderRef ref="accessFile" />
</Root>
</Loggers>
</configuration>
可以修改日志文件产生的路径
<properties>
<!--日志所在的文件夹-->
<property name="LOG_HOME">文件夹路径</property>
</properties>
12. 分页
- 减少数据的处理量
- 控制数据的可控范围
select * from table limit startIndex,pageSize -- [startIndex,startIndex+pageSize-1]
select* from table limit 3 -- [0,3]
代码测试
在接口(UserMapper.java)中添加方法
List<User> getUserByLimit(Map<String,Integer> map);
在UserMapper.xml中实现该方法
<select id="getUserByLimit" parameterType="map" resultType="org.example.pojo.User">
select * from user limit #{startIndex},#{pageSize}
</select>
测试类
@Test
public void getUserByLimitTese(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String,Integer> map=new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> user = mapper.getUserByLimit(map);
for (User user1 : user) {
System.out.println(user1);
}
sqlSession.close();
}
测试结果
MyBatis pagehelper
Mybatis-PageHelper/HowToUse.md at master · pagehelper/Mybatis-PageHelper (github.com)
13. 使用注解开发
13.1 面向接口编程
- 对个体的抽象→抽象体(abstract class)
- 对个体某一方面的抽象→抽象面(intreface)
面向对象:以对象为单位,考虑其属性和方法
面向过程:以具体的流程(事务过程)为单位,考虑其实现
接口与非接口设计:针对复用技术而言,更多的体现对系统整体的架构
13.2 使用注解开发
注解在接口上实现
@Select("select * from user")
List<User> getUsers();
在核心配置文件中绑定接口
<mappers>
<mapper class="org.example.dao.UserMapper"/>
</mappers>
测试
@Test
public void getUsersTese(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
本质:反射机制实现
底层:动态代理
13.3 CRUD
13.3.1 根据id查询用户
在接口中编写方法及注解
//方法存在多个参数,所有的参数之前须加上@Param
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
@Test
public void getUserByIdTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1001);
System.out.println(user);
sqlSession.close();
}
13.3.2 增加新用户
将工具类包中的工具类MyBatisUtils.java中getSession()方法修改如下
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
此时在测试类中可以省略sqlSession.commit();
在接口中编写方法及注解
@Insert("insert into user(name,pwd) values(#{name},#{password})")
int addUser(User user);
测试类
@Test
public void addUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int num=mapper.addUser(new User("user1","123456"));
if (num>0)
System.out.println("插入成功");
sqlSession.commit();
sqlSession.close();
}
13.3.3 删除指定用户
在接口中编写方法及注解
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int id);
测试类
@Test
public void deleteUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int num = mapper.deleteUser(1047);
if (num>0)
System.out.println("删除成功");
sqlSession.commit();
sqlSession.close();
}
13.3.4 更改用户信息
在接口中编写方法及注解
@Update("update user set pwd=#{pwd} where id=#{id}")
int updateUser(@Param("id") int id,@Param("pwd") String pwd);
测试类
@Test
public void updateUserTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int num = mapper.updateUser(1046, "1234123");
if (num>0)
System.out.println("修改成功");
sqlSession.commit();
sqlSession.close();
}
13.4 @Param()注解
- 基本类型的参数或String类型需要加
- 引用类型不需要加
- 只有一个基本类型时可以忽略
- 在SQL中引用的是@Param中设定的属性名
参考博客:【详解】@Param注解的用法_晓风残月一望关河萧索的博客-CSDN博客_@param
14. MyBatis执行流程
15. Lombok
使用步骤
-
安装Lombok插件(IDEA2021自带)
-
在项目中导入Lombok包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
- @Data:无参构造,get,set,toString,hashcode,equals
- @AllArgsConstructor
- @NoArgsConstructor
-
在实体类上添加注解
缺点
- 不支持多种参数构造器的重载
16. 多对一处理
集合与关联
16.1 创建数据库
drop table if exists teacher;
create table if not exists teacher(
id int(10) not null primary key ,
name varchar(30) default null
)engine=innodb default char set =utf8;
insert into teacher(id,name) value (1,'张三');
drop table if exists student;
create table if not exists student(
id int(10) not null primary key ,
name varchar(30) default null,
tid int(10) default null,
key fktid(tid),
constraint fktid foreign key (tid) references teacher(id)
)engine =innodb default char set =utf8;
insert into student(id, name, tid) value
(1,'小明',1),('2','小红','1'),('3','小张','1'),
('4','小李','1'),('5','小王','1');
16.2 测试环境搭建
新建实体类Teacher.java、Student.java
使用Lombok步骤参考上面
//Teacher.java
package org.example.pojo;
import lombok.Data;
@Data
public class Teacher {
private int id;
private String name;
}
//Student.java
package org.example.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
建立Mapper接口
建立Mapper.xml文件
在核心配置文件中绑定注册Mapper接口
<?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>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<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="org\example\dao\TeacherMapper.xml"/>
<mapper resource="org\example\dao\StudentMapper.xml"/>
</mappers>
</configuration>
16.3 查询所有学生信息以及对应的老师信息
//StudentMapper.java
package org.example.dao;
import org.example.pojo.Student;
import java.util.List;
public interface StudentMapper {
//查询所有学生信息以及对应老师的信息
List<Student> getStudent();
}
方式一:查询嵌套
<!--StudentMapper.xml-->
<?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="org.example.dao.StudentMapper">
<resultMap id="student" type="org.example.pojo.Student">
<!--
复杂属性单独处理
对象:association
集合:collection
-->
<association property="teacher" column="tid" javaType="org.example.pojo.Teacher" select="getTeacher"/>
<!--javaType为指定属性的类型,select为编写的select方法-->
</resultMap>
<select id="getStudent" resultMap="student">
select * from student;
</select>
<select id="getTeacher" resultType="org.example.pojo.Teacher">
select * from teacher where id=#{id};
</select>
</mapper>
方式二:结果嵌套
<?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="org.example.dao.StudentMapper">
<resultMap id="student" type="org.example.pojo.Student">
<association property="teacher" javaType="org.example.pojo.Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="getStudent" resultMap="student">
select student.id sid,student.name sname,teacher.name tname
from student
inner join teacher
where student.tid=teacher.id;
</select>
</mapper>
//getStudentTest.java
@Test
public void getStudentTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> student = mapper.getStudent();
for (Student student1 : student) {
System.out.println(student1);
}
sqlSession.close();
}
测试结果
17. 一对多处理
创建数据库
drop table if exists teacher;
create table if not exists teacher(
id int(10) not null primary key ,
name varchar(30) default null
)engine=innodb default char set =utf8;
insert into teacher(id,name) value (1,'张三'),(2,'李四');
drop table if exists student;
create table if not exists student(
id int(10) not null primary key ,
name varchar(30) default null,
tid int(10) default null,
key fktid(tid),
constraint fktid foreign key (tid) references teacher(id)
)engine =innodb default char set =utf8;
insert into student(id, name, tid) value
(1,'小明',1),('2','小红',1),('3','小张',2),
('4','小李',2),('5','小王','1');
实体类
//Student.java
package org.example.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private int tid;
}
//Teacher.java
package org.example.pojo;
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
Mapper接口
//TeacherMapper.java
package org.example.dao;
import org.example.pojo.Teacher;
import java.util.List;
public interface TeacherMapper {
List<Teacher> getTeacher();
}
Mapper.xml文件
<!--TeacherMapper.xml-->
<?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="org.example.dao.TeacherMapper">
<resultMap id="teacher" type="org.example.pojo.Teacher">
<result property="id" column="teacherid"/>
<result property="name" column="tname"/>
<!--集合中的泛型信息使用ofType获取-->
<collection property="students" ofType="org.example.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="stid"/>
</collection>
</resultMap>
<select id="getTeacher" resultMap="teacher">
select teacher.id teacherid,teacher.name tname,student.id sid,student.name sname,student.tid stid
from teacher
inner join student
where student.tid=teacher.id;
</select>
</mapper>
测试类
//getTeacherTest.java
@Test
public void getTeachersTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getTeacher();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
sqlSession.close();
}
测试结果
- 关联-association【多对一】
- 集合-collection【一对多】
- javaType&ofType
- javaType:用来指定实体类中属性的类型
- ofType:用来指定映射到List或集合中的pojo类型,泛型中的约束类型
18. 动态SQL
根据不同条件生成不同的SQL语句
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
18.1 搭建环境
创建数据表
drop table if exists blog;
create table if not exists blog(
id int(50) not null comment '博客ID',
title varchar(100) not null comment '博客标题',
create_time datetime not null comment '创建时间',
views int(10) not null comment '浏览量'
)engine =innodb default char set =utf8;
创建基础工程
//Blog.java
package org.example.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date date;
private int view;
}
生成唯一的通用唯一标识符UUID方法类
package org.example.utils;
import org.junit.Test;
import java.util.UUID;
public class IDUtils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
Mapper接口
//BlogMapper.java
package org.example.dao;
import org.example.pojo.Blog;
public interface BlogMapper {
//插入数据
int addBlog(Blog blog);
}
绑定注册Mapper接口
<mappers>
<mapper resource="org\example\dao\BlogMapper.xml"/>
</mappers>
<!--BlogMapper.xml-->
<?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="org.example.dao.BlogMapper">
<insert id="addBlog" parameterType="org.example.pojo.Blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>
插入数据测试
@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("John");
blog.setCreateTime(new Date());
blog.setViews(100);
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Spring");
blog.setAuthor("Mike");
blog.setViews(456);
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("SpringBoot");
blog.setAuthor("Jerry");
blog.setViews(378);
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Java");
blog.setAuthor("Mike");
blog.setViews(7065);
mapper.addBlog(blog);
sqlSession.close();
}
插入成功但数据库中没有数据的可以在
mapper.addBlog(blog);
后增加
sqlSession.commit();
或者将MyBatisUtils.java工具类中
openSession();
改为
openSession(true);
18.2 IF
Mapper接口中的方法
List<Blog> queryBlogIF(Map map);
BlogMapper.xml中的配置
<select id="queryBlogIF" parameterType="map" resultType="org.example.pojo.Blog">
select * from mybatis.blog
<where>
<if test="title !=null">
and title=#{title}
</if>
<if test="author !=null">
and author=#{author}
</if>
</where>
</select>
测试类
@Test
public void queryBlogIFTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("title","Java");
hashMap.put("author","Mike");
List<Blog> blogs = mapper.queryBlogIF(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
测试结果
如果没有参数,会输出数据库中所有的数据
当传入的参数为空时,没有输出
18.3 choose (when, otherwise)
Mapper接口中的方法
List<Blog> queryBlogChoose(Map map);
BlogMapper.xml中的配置
<select id="queryBlogChoose" parameterType="map" resultType="org.example.pojo.Blog">
select * from mybatis.blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>
<when test="author!=null">
and author=#{author}
</when>
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
测试类
@Test
public void queryBlogChooseTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("title","Java");
hashMap.put("author","Mike");
hashMap.put("views",456);
List<Blog> blogs = mapper.queryBlogChoose(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
测试结果
在匹配时只会搜索第一个满足的条件,后续条件自动跳过
18.4 trim (where, set)
18.4.1 where
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
where元素不会自从添加and
where的使用可以参考上方BlogMapper.xml文件
18.4.2 set
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
author=#{author}
</if>
</set>
where id=#{id}
</update>
18.5 SQL片段
18.5.1 使用SQL标签抽取公共部分
<sql id="if-title-author">
<if test="title !=null">
title=#{title}
</if>
<if test="author !=null">
and author=#{author}
</if>
</sql>
18.5.2 使用include标签引用
<select id="queryBlogIF" parameterType="map" resultType="org.example.pojo.Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
- 基于单表定义SQL片段
- 不要存在where标签
18.6 foreach
Mapper接口中的方法
List<Blog> queryBlogForeach(Map map);
BlogMapper.xml中的配置
<select id="queryBlogForeach" parameterType="map" resultType="org.example.pojo.Blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
测试类
@Test
public void queryBlogForeachTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
hashMap.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
当ArrayList为空时,会遍历数据表中所有的数据
19. 缓存
什么是缓存
- 存在内存中的临时数据
为什么使用缓存
- 提高查询效率,解决高并发系统的性能问题
- 减少和数据库的交互次数,减少系统开销,提高系统效率
什么样的数据能使用缓存
- 经常查询且不经常改变的数据
19.1 MyBatis缓存
MyBatis可以非常方便的定制和配置缓存。在MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。默认情况下只有一级缓存开启
-
一级缓存:基于SqlSession级别的缓存,也称为本地缓存
-
二级缓存:基于namespace级别的缓存,需要手动开启和配置
-
可以通过Cache接口自定义二级缓存
可用的清除策略
LRU
– 最近最少使用:移除最长时间不被使用的对象。默认FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
19.2 一级缓存
@Test
public void queryUserByIdTest(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1001);
System.out.println(user);
System.out.println("====================");
User user2 = mapper.queryUserById(1001);
System.out.println(user2);
sqlSession.close();
}
测试结果(日志输出)
在同一个SqlSession中SQL语句只执行了一次
缓存失效的情况
查询的数据不同
增删改操作会改变原来缓存中的数据
查询不同的Mapper.xml
手动清理缓存
sqlSession.clearCache();//手动清理缓存
19.3 二级缓存
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
使用步骤
-
显式的开启全局缓存
<setting name="cacheEnabled" value="true"/>
-
在Mapper.xml中开启二级缓存
<!--在当前Mapper.xml中使用二级缓存--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的-->
-
测试
@Test public void CashTest(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); SqlSession sqlSession1 = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); User user = mapper.queryUserById(1001); System.out.println(user); sqlSession.close(); System.out.println("================="); User user2 = mapper1.queryUserById(1001); System.out.println(user2); sqlSession1.close(); }
测试结果
-
在测试过程中可能会出现如下问题
Caused by: java.io.NotSerializableException: org.example.pojo.User
此时有两种解决办法
- 在实体类中实现Serializable接口
- 在Mapper.xml中配置二级缓存readOnly=true
实现Serializable接口后两次读取的对象哈希值不同(序列化为深拷贝,反序列化后的对象和原对象不是一个对象)