MyBatis笔记
MyBatis笔记
一.概述
MyBatis原是Apache的一个开源项目iBatis, 2010年由Apache Software Foundation 迁移到了 Google Code, iBatis3.x正式更名为MyBatis。是 一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects (DAO)。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录,是 一种 ORM(ORM Object Relational Mapping 对象关系映射)实现. Mybatis 将基本的 JDBC 常用接口封装,对外提供操作即可.
- 是一个ORM框架.
ORM(Object Relational Mapping)对象关系映射 - 封装了一些关于数据库操作的接口,代替了JDBC
- 对jdbc进行了封装,简化了其操作
Mybatis 中文官网: mybatis – MyBatis 3 | 入门
既然mybatis封装了JDBC,那么来看一下传统JDBC和MyBatis的区别
我们使用mybatis做一个小demo来看一下和传统JDBC的区别
使用maven构建一个Mybatis,在这里我使用的工具是IDEA
传统JDBC:
- 加载数据库驱动
- 创建并获取数据库链接
- 创建 statement 对象
- 拼写 sql 语句
- 设置 sql 语句中的占位符的值 (使用预编译可以防止sql注入)
- 执行 sql 语句并获取结果
- 对 sql 执行结果进行解析处理
- 释放资源
JDBC的编程问题
- 数据库连接的创建、释放频繁造成系统资源浪费从而影响系统性能,如果使 用数据库连接池可解决该问题。
- SQL 语句编写在 Java 代码中,这种硬编码造成代码不易维护,当 SQL 变动 时需要修改 java 源代码。
- 使用 preparedStatement 向占位符传参数存在硬编码,因为 SQL 语句的 where 条件中占位符的个数可能会变化,修改 SQL 还要修改 Java 源代码, 系统不易维护.
- 对结果集解析存在硬编码,SQL 语句变化导致解析代码变化,系统不易维护。
二. MyBatis 环境搭建
-
先创建一个maven的项目(搭建maven:https://blog.csdn.net/lanleihhh/article/details/120978091)
-
导入mybatis/mysql的坐标(maven仓库下载:https://mvnrepository.com/)
复制后粘贴到中
<!-- 导入 MyBatis jar 包数据库驱动包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- 导入mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
点击’M’形状的按钮安装jar包
我们可以使用日志组件(log4j)来记录程序的信息,引入log4j的坐标
<!-- 导入log4j jar包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
注意:引入log4j的坐标后,还需在resources文件中添加配置文件:log4j.properties
log4j.rootLogger = debug,stdout,D
#System out Console
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 = [%p] %d{yyyy-MM-dd HH:mm:ss,SSS} %m%n
#System out File
log4j.appender.D = org.apache.log4j.FileAppender
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
# 这里设置打印日志的文件路径
log4j.appender.D.File = E://logs/mybatisLog/LogMybatisDemo.log
log4j.appender.D.Append = true
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] -[%l] %m%n
创建 MyBatis 全局配置文件 MyBatis 的配置文件包含了对 MyBatis 行为的设置信息。
配置文档的顶层 结构如下(标签需要按照特定顺序排放):
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
如下:
<?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"></transactionManager>
<!--数据源-->
<dataSource type="POOLED">
<!--属性-->
<property name="driver" value="" />
<property name="url" value="" />
<property name="username" value="" />
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<!--映射器-->
<mappers>
<mapper resource="映射文件全类名.xml"/>
</mappers>
</configuration>
三.创建Mybatis项目步骤
步骤:
1.根据需求设计一个类与数据库的表
Java类:
package com.ffyc.mybatispro.model;
public class Admin {
private int id;
private String account;
private String password;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Admin{" +
"id=" + id +
", account='" + account + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", userName='" + userName + '\'' +
'}';
}
}
数据库表:
CREATE DATABASE mybatis_db CHARSET utf8
CREATE TABLE admin(
id INT PRIMARY KEY AUTO_INCREMENT,
sex CHAR(1),
account VARCHAR(20),
PASSWORD VARCHAR(20)
)
2.接口(Mapper),实现对类的一些操作
package com.ffyc.mybatispro.mapper;
import com.ffyc.mybatispro.model.Admin;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface AdminMapper {
//插入
void saveAdmin(Admin admin);
//更新
void updateAdmin(Admin admin);
//删除
void deleteAdmin(int AdminId);
//修改
Admin findAdminById(int AdminId);
//传多个参数,使用注解标签@Param("acc") acc:传向xml文件中 #{acc}
Admin findAdmin(@Param("acc")String account,@Param("sex")String sex);
//传入键值对参数
Admin findAdmin(Map<String,Object> map);
//查询所有管理员对象
List<Admin> findAllAdmin();
//查询管理员总人数
int adminCount();
}
3.全局配置文件(mybatis.xml)配置信息
几个.xml配置文件的区别
< typeAliases>
为类定义别名
- type:全类名
- alias:别名
<!--为类定义别名-->
<typeAliases>
<typeAlias type="com.ffyc.mybatispro.model.Admin" alias="Admin"></typeAlias>
</typeAliases>
配置< mapper>
(添加SQL映射文件)
<!--添加SQL映射文件-->
<mapper resource="mapper/AdminMapper.xml"/>
设置
Mybatis日志
Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING
- NO_LOGGING
具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的配置日志
<settings>
<!-- 配置日志实现, 使用log4j,添加log4j -->
<setting name="logImpl" value="LOG4J"/>
</settings>
配置环境
<environments default="development">
<!--配置与数据库连接的相关信息-->
<environment id="development">
<!--配置事务管理类型:JDBC-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源:配置连接池与数据库连接的参数
UNPOOLED:每次创建新的连接,用完销毁
POOLED:使用已有的连接,用完返回
-->
<dataSource type="UNPOOLED">
<!-- ${} 拼接符,会传入参数字符串
我们创建一个properties文件用来存储连接数据库的信息 -->
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
创建config. properties(存储与数据库连接的信息)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=lanlei6843
引入config. properties
<!--引入存放值的属性文件 -->
<properties resource="config.properties"></properties>
4.接口对应的配置文件(AdminMapper.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">
<!-- namespace:命名空间,写接口的全类名-->
<mapper namespace="com.ffyc.mybatispro.mapper.AdminMapper">
<!--
id:与接口中方法名相同
parameterType:参数类型,简单类型(int等)可以不写此属性
resultType:返回值类型
useGeneratedKeys:可以返回刚插入数据的主键
keyColumn:告知主键列
keyProperty:告知主键列对应的属性
-->
<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admin(account,password,sex,user_name)values(#{account},#{password},#{sex},#{userName})
</insert>
</mapper>
测试方法:saveAdmin()
package com.ffyc.mybatispro.test;
import com.ffyc.mybatispro.mapper.AdminMapper;
import com.ffyc.mybatispro.model.Admin;
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.io.Reader;
public class Test1 {
public static void main(String[] args) throws IOException {
Admin admin = new Admin();
admin.setAccount("admin");
admin.setPassword("123");
admin.setSex("男");
/*
1.读取 mybatis 核心配置文件
sql写在xml文件中,我们不能直接访问,mybatis让我们在业务代码中可以定义接口,在接口中定义操作的方法
方法定义要求:
方法名=xml中对应的标签的id
方法中的参数类型和返回值类型与xml中一致
* */
InputStream reader = Resources.getResourceAsStream("mybatis.xml");
/*
2.创建sqlSessionFactory对象
创建sqlSession的工厂,负责创建sqlSession对象 sqlSessionFactory中包含核心配置信息
由于sqlSessionFactory对象创建开销较大,所以一旦创建后就不会销毁,一个应用程序只有一个sqlSessionFactory
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
/*
3.创建sqlSession对象,表示一次与数据库连接会话,类似于JDBC的Connection
每次与数据库交互,都会创建一个sqlSession对象,使用完后会立即销毁释放
*/
SqlSession sqlSession= sqlSessionFactory.openSession();
//获得映射接口的代理对象
//MapperProxy动态实现了自定义接口
//adminMapper对象实质是MapperProxy对象 org.apache.ibatis.binding.MapperProxy@79ad8b2f
//使用代理对象
AdminMapper adminMapper = sqlSession.getMapper(AdminMapper.class);
adminMapper.saveAdmin(admin);
sqlSession.commit();//提交事务
sqlSession.close();//关闭与数据库会话连接
}
}
运行一下:
查看数据库:
我们使用另一中传值方式
<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admin(account,password,sex)values('${account}','${password}','${sex}')
</insert>
运行:
传列: ${属性名}
传值:
'${属性名}':${} 拼接符,会传入参数字符串,取值以后再去编译 SQL 语句,$方式无法防止sql注入
#{属性名} :#{} 占位符,是经过预编译的,编译好 SQL 语句再取值,#方式能够防止 sql 注入
values('','',''):字符串拼接方式
values(?,?,?):预编译,是安全的,可以防止sql注入
注意:MyBatis 排序时使用 order by 动态参数时需要注意,用$而不是#
驼峰映射
在mybatis中默认java属性名和数据库字段名一一对应
如:
java: account
mysql:account
但两者定义语法不同
- java属性采用小驼峰(userName)
- 数据字段采用下划线(user_name)
很多时候没法将Java属性与数据库字段定义为相同的,因为要遵循规范
解决方法:
-
在sql语句中使用As设置别名,如(user_name As userName),设置与Java属性相同
-
mybatis的settings配置中有一个
name
为mapUnderscoreToCamelCase
的参数,默认是false,设置为true,即可开启驼峰映射可以从Java属性userName映射到数据库字段user_name,方便了很多
<!--开启驼峰映射 设置启用数据库字段下划线映射到java对象的驼峰式命名属性,默认为false-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
我们测试一下
在数据库中修改表列命名为经典下划线
-- 修改列名
ALTER TABLE admin CHANGE PASSWORD pass_word VARCHAR(20)
在java中修改属性
//private String password;原属性
private String passWord;
sql映射中修改sql语句
insert into admin(account,pass_word,sex)values(#{account},#{passWord},#{sex})
测试运行:
成功运行了,这就是驼峰映射
我们将属性与字段再修改为原来的password
封装步骤
将步骤繁多的流程封装起来,我们使用的时候直接访问该封装类的方法
package com.ffyc.mybatispro.util;
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 MybatisUtil {
static SqlSessionFactory sqlSessionFactory = null;
static {
/*1.读取 mybatis 核心配置文件*/
InputStream reader = null;
try {
reader = Resources.getResourceAsStream("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
/*2.创建sqlSessionFactory对象*/
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession getSqlSession(){//返回sqlSession对象
return sqlSessionFactory.openSession();
}
}
单元测试
概念
-
java单元测试是最小的功能单元测试代码
-
单元测试就是针对某个Java方法的测试
-
java程序的最小功能单元是方法
使用单元测试的好处
- 不用再在main()方法中一个一个调用,测试完毕后再删除
- 确保单个方法正常运行
- 每个单元测试用例相对独立,不需要添加额外语句
- 只需查看测试结果,就可以了解整个项目的的方法接口是否通畅
我们借助juit工具进行测试
先在pom.xml中依赖单元测试的jar包,将其jar包下载到本地仓库中,然后依赖它
<!-- 依赖单元测试 jar包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
在想要测试的方法前添加注解标签@Test,如下:
@Test
public void update(){
Admin admin = new Admin();
admin.setAccount("admin");
admin.setPassword("123456");
admin.setSex("男");
//创建SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取代理对象
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
mapper.updateAdmin(admin);
sqlSession.commit();//提交事务
sqlSession.close();//关闭连接
}
类型别名
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<!--为类定义别名-->
<typeAliases>
<typeAlias type="com.ffyc.mybatispro.model.Admin" alias="Admin"></typeAlias>
<typeAlias type="com.ffyc.mybatispro.model.Dept" alias="Dept"></typeAlias>
<typeAlias type="com.ffyc.mybatispro.model.Employee" alias="Employee"> </typeAlias>
</typeAliases>
当这样配置时,Admin
可以在任何使用 com.ffyc.mybatispro.model.Admin
的地方。
可以在mybatis的jar包中找org.apache.ibatis.type.TypeAliasRegistry
里面定义了各种基本类型和引用类型的别名;如图
下面是mybatis为Java类库中的一些类和简单类型定义的别名
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
参数传递
1.简单的参数形式不需要使用 parameterType 参数定义
如:
//删除
void deleteAdmin(int AdminId);
//修改
Admin findAdminById(int AdminId);
<!--删除-->
<delete id="deleteAdmin">
delete from admin where id=#{id}
</delete>
<!--查找-->
<select id="findAdminById" resultType="Admin">
SELECT * FROM admin WHERE id=#{id}
</select>
2. 多个参数使用@Param(“id”)绑定
Mapper接口方法
//传多个参数,使用注解标签@Param("acc") acc:传向xml文件中 #{acc}
Admin findAdmin(@Param("acc")String account,@Param("sex")String sex);
xml中的实现
<select id="findAdmin" resultType="Admin">
SELECT * FROM admin WHERE account=#{acc} and sex=#{sex}
</select>
java测试代码
@Test
public void find3(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
Admin admin = mapper.findAdmin("admin","男");
System.out.println(admin);
sqlSession.commit();
sqlSession.close();
}
3.传入复杂的参数,使用Map对象传递
Mapper接口方法
//传入键值对参数
Admin findAdmin(Map<String,Object> map);
xml中的实现
对于简单类型可不写parameterType属性,使用Java类库中的类作为参数时,使用mybatis为其定义的别名
这里可以通过mybatis3官方手册查看,XML配置–>类型别名https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
<!-- java中已有的类作为类型使用时,可以直接使用mybatis定义的别名 -->
<select id="findAdmin" resultType="Admin" parameterType="hashmap">
SELECT * FROM admin WHERE account=#{acc} and sex=#{sex}
</select>
java测试代码
@Test
public void find4(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("acc", "admin");
map.put("sex", "男");
Admin admin = mapper.findAdmin(map);
System.out.println(admin);
sqlSession.commit();
sqlSession.close();
}
结果处理
简单类型输出映射
返回基本类型
//查询管理员总人数
int adminCount();
<!--resultType:返回值类型 id:接口中方法名-->
<select id="adminCount" resultType="int">
SELECT count(*) FROM admin
</select>
@Test
public void find6(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
int count = mapper.adminCount();
System.out.println("管理员总人数:"+count);
sqlSession.commit();
sqlSession.close();
}
POJO对象输出映射
- 如果表中的类名与类中的属性名完全相同,mybatis会自动将查询结果封装 到POJO对象中
- 如果java中使用标准驼峰命名,数据库中使用下划线连接命名,可以开启全局设置实现自动转换
resultMap:
- resutlMap 的 id 属性是 resutlMap 的唯一标识
- resutlMap 的 id 属性是映射的 POJO 类
- id 标签映射主键,result 标签映射非主键
- property 设置 POJO 的属性名称,column 映射查询结果的列名称
<setting name="mapUnderscoreToCamelCase" value="true"/>
将admin表中列名password修改为pass_word
<select id="findAdminById" resultMap="adminMap">
SELECT id,account,pass_word,sex FROM admin WHERE id=#{id}
</select>
定义resultMap
<!--结果集(用于多表关联,此处测试列名与属性名不同时的处理)-->
<resultMap id="adminMap" type="Admin">
<id column="id" property="id"></id>
<result column="account" property="account"></result>
<result column="pass_word" property="password"></result>
</resultMap>
多表关联处理查询结果集
-
association – 复杂类型联合
许多查询结果合成这个类型 一对一结果映射
association 能引用自身, 或者从其它地方引用
处理一个类型的关系(类)
-
collection – 复杂类型集合 嵌套结果映射
collection 能引用自身, 或者从其它地方引用 多对一与一对多
处理一对多关联(集合)
-
部门与员工一对多
-
多个员工对应一个部门(多对一)
-
package com.ffyc.mybatisdemo.mapper;
import com.ffyc.mybatisdemo.model.Dept;
import java.util.List;
public interface DeptMapper {
//根据id查部门
Dept findDeptById(int id);
//查所有部门
List<Dept> findDeptList();
}
sql映射文件
<?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.ffyc.mybatisdemo.mapper.DeptMapper">
<select id="findDeptById" resultMap="deptMap">
SELECT
d.id,
d.name dname,
e.name ename,
a.account
FROM dept d LEFT JOIN employee e ON d.id=e.dept_id
LEFT JOIN admin a ON d.admin_id=a.id
WHERE d.id=1
</select>
<select id="findDeptList" resultMap="deptMap">
SELECT
d.id,
d.name dname,
e.name ename,
a.account
FROM dept d LEFT JOIN employee e ON d.id=e.dept_id
LEFT JOIN admin a ON d.admin_id=a.id
</select>
<!--使用resultMap组装查询结果-->
<resultMap id="deptMap" type="Dept">
<id column="id" property="id"></id>
<result column="dname" property="name"></result>
<association property="admin" javaType="Admin">
<result property="account" column="account"></result>
</association>
<collection property="employeeList" javaType="list" ofType="Employee">
<result column="ename" property="name"></result>
</collection>
</resultMap>
</mapper>
测试根据id查部门
@Test
public void findDept(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findDeptById(1);
List<Employee> deptList = dept.getEmployeeList();
System.out.println("部门"+dept.getName());
for(Employee employee : deptList){
System.out.println("所属员工:"+employee.getName());
}
sqlSession.commit();
sqlSession.close();
}
测试查所有部门
@Test
public void findDeptList(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
List<Dept> depts = mapper.findDeptList();
for(Dept dept : depts){
System.out.println("部门名称:"+dept.getName());
for(Employee emp : dept.getEmployeeList()){
System.out.println("部门职工:"+emp.getName());
}
System.out.println("操作人:"+dept.getAdmin().getAccount());
}
sqlSession.commit();
sqlSession.close();
}
延迟加载&嵌套查询
需要查询关联信息时,使用 Mybatis 延迟加载特性可以有效的减少数据库压力, 首次查询只查询主表信息,关联表的信息在用户获取时再加载。
Mybatis 一对一关联的 association
和一对多的 collection
可以实现延迟加载。加载时要使用 resultMap
,不能使用 resultType。
启用延迟加载(懒加载)
<settings>
<!--延迟加载的全局开关。 默认值false
当开启时,所有关联对象都会延迟加载通过设置fetchType属性来覆盖该项的开关状态。-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
指定触发延迟加载的方法
<!--延迟加载 lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载
默认值:equals,clone,hashCode,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>
来一个实例展示一下延迟加载的效果,将原本多表关联的sql语句拆分为嵌套查询
原多表关联查询
<select id="findEmployeeById" resultMap="employeeMap">
SELECT
e.name ename,
e.age,
e.sex,
d.name dname,
d.id did,
a.account
FROM employee e
LEFT JOIN dept d ON e.dept_id=d.id
LEFT JOIN admin a ON e.admin_id=a.id
WHERE e.id=#{id}
</select>
嵌套查询
<!--懒加载,不能用resultType,只能用resultMap-->
<select id="findEmployeeByIdTestLazy" resultMap="employeeMap">
select name,age,sex,dept_id,admin_id from employee where id=#{id}
</select>
<select id="findAdminTestLazy" resultType="Admin">
select account from admin where id=#{admin_id}
</select>
<select id="findDeptTestLazy" resultType="Dept">
select name from dept where id=#{dept_id}
</select>
结果集
<resultMap id="employeeMap" type="Employee">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--
fetchType:lazy 开启延迟加载(需要时采取触发查询)前提:将多表查询(一条sql)写成嵌套查询(多条sql)
select:与select标签的id值相同
-->
<association property="admin" column="admin_id" javaType="Admin"
fetchType="lazy" select="findAdminTestLazy">
<result property="account" column="account"></result>
</association>
<association property="dept" column="dept_id" javaType="Dept"
fetchType="lazy" select="findDeptTestLazy">
<result column="name" property="name"></result>
</association>
</resultMap>
现在测试一下延迟加载
@Test//查询单个员工(测试延迟加载)
public void find3(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//在调用方法出打断点,来看一看延迟加载;正常程序应该是逐条执行,一次性取出employee对象的属性,我们来试一下
Employee employee = mapper.findEmployeeByIdTestLazy(1);//打断点
System.out.println(employee.getName());
System.out.println(employee.getDept().getName());
System.out.println(employee.getAdmin().getAccount());
sqlSession.commit();
sqlSession.close();
}
注解方式
常用注解标签
- @Insert : 插入 sql , 和 xml insert sql 语法完全一样
- @Select : 查询 sql, 和 xml select sql 语法完全一样
- @Update : 更新 sql, 和 xml update sql 语法完全一样
- @Delete : 删除 sql, 和 xml delete sql 语法完全一样
- @Param : 传入参数
- @Results : 设置结果集合
- @Result : 结果
//简单sql语句可以直接写在接口方法的注解标签中,不用在xml文件中写
@Delete("DELETE FROM employee WHERE id=#{id}")
//@Param绑定参数,将其传给sql语句中的占位符处
void del(@Param("id") int id);
//删除
@Test
public void del1(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.del(4);//删除id为4的记录
sqlSession.commit();
sqlSession.close();
}
执行方法
查询所有信息
@Select("select * from t_emp")
@Results(id = "empMap",value = {
@Result(column = "emp_id",property = "empId",id = true),
@Result(column = "emp_name",property = "empName"),
@Result(column = "emp_tel",property = "empTel"),
@Result(column = "emp_education",property = "empEducation"),
@Result(column = "emp_birthday",property = "empBirthday")
})
List<Employee> getAll();//接口方法
这几条注解标签相当于sql映射(.xml)文件中的resultMap
,写起来相对麻烦一些
插入信息
@Insert("insert into t_emp (emp_id, emp_name, emp_tel, " +
" emp_education, emp_birthday, fk_dept_id" +
" )" values (#{empId}, #{empName}, #{empTel}, " +
" #{empEducation}, #{empBirthday}, #{fkDeptId}" +
" )")
int insert(Employee record);
在注解标签中写sql语句和传统JDBC一样,需要拼接许多字符串,而sql映射文件中不存在拼接字符串的情况
总结:
-
如单个查询/删除等简单sql语句,可以直接在接口的方法上方使用注解标签写sql
-
若sql语句较长,查询结果较多,写在映射文件中更简单方便一些
动态sql
我们以前使用传统的JDBC写复杂一点的sql语句时,往往需要拼接字符串,容易因为标点符号错误或缺失等导致整条sql是不能运行的.Mybatis提供了动态sql来解决以前令人头疼的事.
动态sql可以根据用户提供的参数,动态地决定查询语句的条件或sql语句的内容
Mybatis中用于实现动态sql的元素:
- if
- where
- trim
- set
- choose(when,otherwise)
- foreach
if元素
if标签可以对传入的条件进行判断
<select id="findEmployee" resultMap="employeeMap">
SELECT
e.id,
e.name ename,
e.age,
e.sex,
d.name dname,
a.account
FROM employee e
LEFT JOIN dept d ON e.dept_id=d.id
<!-- where 1=1 不管name和age是否为空,总会正常执行-->
LEFT JOIN admin a ON e.admin_id=a.id where 1=1
<if test="name!=null & name !=''">
and e.name=#{name}
</if>
<if test="age!=null & age !='' ">
and e.age=#{age}
</if>
</select>
where
当查询条件的个数不确定的情况下,使用where
元素.
可以根据里面if
是否有成立的,动态添加where
关键字,会自动删除and
or
等开头的关键字
<select id="findEmployee" resultMap="employeeMap">
SELECT
e.id,
e.name ename,
e.age,
e.sex,
d.name dname,
a.account
FROM employee e
LEFT JOIN dept d ON e.dept_id=d.id
LEFT JOIN admin a ON e.admin_id=a.id
<where>
<if test="name!=null & name !=''">
e.name =#{name}
</if>
<if test="age!=null & age !='' ">
and e.age =#{age}
</if>
</where>
</select>
set
set元素可以去掉set语句中的最后一个逗号
<update id="updateEmp" parameterType="Employee">
update employee
<set>
<if test="name!=null">
name =#{name},
</if>
<if test="sex!=null">
sex =#{sex},
</if>
<if test="dept.id!=null">
dept_id=#{dept.id},
</if>
</set>
where id=1
</update>
@Test
public void update(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setName("王玄策");
employee.setSex("男");
Dept dept = new Dept();
dept.setId(2);
employee.setDept(dept);
mapper.updateEmp(employee);
sqlSession.commit();
sqlSession.close();
}
trim
使用trim元素也可以实现where 和 set 元素的功能
trim实现where
<!--前缀为where-->
<select id="findEmployee" resultMap="employeeMap">
SELECT
e.id,
e.name ename,
e.age,
e.sex,
d.name dname,
a.account
FROM employee e
LEFT JOIN dept d ON e.dept_id=d.id
LEFT JOIN admin a ON e.admin_id=a.id
<trim prefix="where" prefixOverrides="and|or">
<if test="name!=null & name !=''">
and e.name =#{name}
</if>
<if test="age!=null & age !='' ">
and e.age =#{age}
</if>
</trim>
</select>
实现set
<!--前缀为set-->
<update id="updateEmp" parameterType="Employee">
update employee
<trim prefix="set" suffix="where" suffixOverrides=",">
<if test="name!=null">
name =#{name},
</if>
<if test="sex!=null">
sex =#{sex},
</if>
<if test="dept.id!=null">
dept_id=#{dept.id},
</if>
</trim>
id=1
</update>
choose(when,otherwise)
多路选择,和Java中的switch类似
when相当于switch中的case,当<when test="条件 ">
条件成立,执行when中的语句
otherwise则类似default,当都不成立时,执行它
<!-- &是&的转义符 -->
<select id="findEmployee" parameterType="Employee" resultMap="employeeMap">
SELECT
e.id,
e.name ename,
e.age,
e.sex,
d.name dname,
a.account
FROM employee e
LEFT JOIN dept d ON e.dept_id=d.id
LEFT JOIN admin a ON e.admin_id=a.id
<trim prefix="where" prefixOverrides="and">
<choose>
<when test="name!=null & name!='' ">
and e.name =#{name}
</when>
<when test="age!=null & age!='' ">
and e.name='李四'
</when>
<otherwise>
and e.name='里利'
</otherwise>
</choose>
</trim>
</select>
当第一个when的条件成立时(name值不为空or空串)
当第二个when的条件成立时(age不为空时)
当条件不成立时(执行otherwise里的语句)
foreach
主要用在构建In中
SELECT * FROM employee WHERE age IN(20,22,24)
可以在 SQL 语句中进行迭代一个集合。
主要属性:
-
item: 表示集合中每一个元素进行迭代时的别名
-
index: 指定一个名字,用于 表示在迭代过程中,每次迭代到的位置
-
open: 表示该语句以什么开始
-
separator: 表示在每次进行迭代之间以什么符号作为分隔符
-
close: 表示以什么结束
-
collection: 该属性是必须指定的,但是在不同情况下,该属性的值是不一样的
- 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
- 如果传入的是单参数且参数类型是一个 array 数组的时候, collection 的属性值为 array
- 使用集合作为参数,测试foreach的效果
//根据年龄集合查找年龄范围内的员工
List<Employee> findEmployeeByAge(List<Integer> list);
<select id="findEmployeeByAge" resultType="Employee">
select * from employee where age in
<foreach item="age" collection="list" open="(" separator="," close=")">
#{age}
</foreach>
</select>
@Test//传入集合测试foreach
public void find4(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Integer> list = new ArrayList<>();
list.add(20);
list.add(21);
list.add(22);
mapper.findEmployeeByAge(list);
sqlSession.commit();
sqlSession.close();
}
- 使用数组作为参数,测试foreach的效果
//根据年龄数组查找年龄范围内的员工
List<Employee> findEmployeeByAge1(Integer[] arr);
<select id="findEmployeeByAge1" resultType="Employee">
select * from employee where age in
<foreach item="age" collection="array" open="(" separator="," close=")">
#{age}
</foreach>
</select>
@Test//传入数组测试foreach
public void find5(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Integer[] arr = new Integer[]{20,21,22};
mapper.findEmployeeByAge1(arr);
sqlSession.commit();
sqlSession.close();
}
转义符
在 mybatis 中的 xml 文件中,存在一些特殊的符号,比如:<、>、"、’&、<> 等,正常书写 mybatis 会报错,需要对这些符号进行转义。具体转义如下所示: 特殊字符 转义字
-
<
<
-
>
>
-
"
"
-
'
'
-
&
&
<!--查询年龄小于指定参数的员工-->
<select id="findEmployeeByAge2" resultType="Employee">
select * from employee where age < #{age}
</select>
@Test//找年龄<21的
public void find6(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
}
除了可以使用上述转义字符外,还可以使用<![CDATA[ ]]>
包裹特殊字符 . <![CDATA[ ]]>
是 XML 语法。在 CDATA 内部的所有内容都会被解析器忽略。
<select id="findEmployeeByAge2" resultType="Employee">
select * from employee where age <![CDATA[ < ]]> #{age}
</select>
我们可以将需要转义才能被解析的字符放在<![CDATA[ 特殊符号 ]]>
中
mybatis缓存
为什么使用缓存?
由于从硬盘读取数据相对来说比较慢,当大批量查询(淘宝双11点击量暴增)时,数据库的压力较大,可以将第一次查询到的数据放在内存中而不销毁它,下次若查询该数据时可以直接从内存中获取,减少对数据库的查询次数,提高了数据库的性能.
缓存是使用 Map 集合缓存数据的 .
- 一级缓存:sqlSession级别,在一次会话中保存数据,会话结束后缓存中的数据销毁
- 二级缓存: SqlSessionFactory级别的
一级缓存
Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession, 在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库 中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存 .
我们使用上面的按年龄查询的方法测试mybatis的一级缓存
两个对象执行相同的查询,查询次数为2,因为一级缓存在与同一个sqlSession对象中
@Test//找年龄<21的
public void find6(){
//sqlSession对象1
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
//sqlSession对象2
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
mapper1.findEmployeeByAge2(21);
sqlSession1.commit();
sqlSession1.close();
}
一个对象执行两次相同的查询,只查询了一次
@Test//找年龄<21的
public void find6(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
}
一个对象执行两次不同的查询
@Test//找年龄<21的
public void find6(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
mapper.findEmployeeByAge2(22);
sqlSession.commit();
sqlSession.close();
}
生命周期
- MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象, SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象,如果 SqlSession 调用了 close()方法,会释放掉一级 缓存 PerpetualCache 对象,一级缓存将不可用。
- 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象 中的数据,但是该对象仍可使用。
- SqlSession 中执行了任何一个 update 操作(update()、delete()、 insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用
清空一级缓存
-
close()关闭会话,清空一级缓存
-
clearCache()清空一级缓存
-
增删改清空一级缓存
clearCache()清空一级缓存
@Test
public void find7(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
//clearCache清空一级缓存的数据
sqlSession.clearCache();
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
}
增删改清空一级缓存
@Test//增删改会清空当前SQLSession对象中的缓存(防止数据脏读)
public void find8(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
Employee employee = new Employee();
employee.setName("王玄策123");
employee.setSex("男");
Dept dept = new Dept();
dept.setId(2);
employee.setDept(dept);
mapper.updateEmp(employee);
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
}
二级缓存
二级缓存是SqlSessionFactory级别的(SqlSessionFactory只有一个),根据Mapper的namespace
划分区域的.
相同 namespace
的 mapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace
都不同,此时可以理解为二级缓存区域是根据 mapper 划分。
每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。 sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。
配置二级缓存
-
启用二级缓存
mybatis默认不开启二级缓存
<setting name="cacheEnabled" value="true"/>
-
POJO序列化(所有)
所有POJO类(自定一类)实现序列化接口( Java.io. Serializable )
-
配置映射文件(
<cache/>
)在 Mapper 映射文件中添加
<cache/>
,表示此 mapper 开启二级缓存当 SqlSeesion 关闭时,会将数据存入到二级缓存
当一个SqlSession对象关闭后(一次会话结束),数据存入二级缓存
SqlSessionFactory中的其他SqlSession对象依旧可以使用二级缓存中的数据
@Test
public void find9(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
mapper.findEmployeeByAge2(21);
sqlSession.commit();
sqlSession.close();
//sqlSession关闭后,将数据存放到二级缓存中,同一个sqlSessionFactory的sqlSession对象共缓存
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
mapper1.findEmployeeByAge2(21);
sqlSession1.commit();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
mapper2.findEmployeeByAge2(21);
sqlSession2.commit();
}
类型处理器(typeHandlers) 入参和查询结果 将java类型与数据库类型进行匹配转账
- BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
- ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERIC 或 BYTE
- ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERIC 或 SMALLINT
- IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERIC 或 INTEGER
- LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERIC 或 BIGINT
- FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERIC 或 FLOAT
- DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERIC 或 DOUBLE
- BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC 或 DECIMAL
- StringTypeHandler java.lang.String 数据库兼容的 CHAR, VARCHAR
- ClobReaderTypeHandler java.io.Reader -
- ClobTypeHandler java.lang.String
数据库兼容的 CLOB, LONGVARCHAR