MyBatis
1 MyBatis 概念
为什么要使用MyBatis
:- 编码繁琐
- 需要我们自己将结果集映射成对象
- 性能不太好 连接池 缓存
- SQL语句和java代码的耦合度特别高
- ….
介绍
- MyBatis 本是Apache的一个开源项目iBatis, 2010年这个项目由Apache Software Foundation 迁移到了Google Code,且改名为MyBatis
- 2013年11月迁移到GitHub。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架
- MyBatis 是一款优秀的
持久层
框架,它支持定制化 SQL、存储过程以及高级映射
- MyBatis 避免了几乎所有的
JDBC
代码和手动设置
参数以及获取结果集 - MyBatis 可以使用简单的
XML
或注解
来配置和映射原生信息,将接口和 Java 的POJOs(Plain Ordinary Java Object,普通的 Java对象)
映射成数据库中的记录
1.1 认识框架
框架(Framework)
是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。框架这个广泛的定义使用的十分流行,尤其在软件概念框架(Framework)
对于Java
来说,就是一系列为了解决特定问题而定义的一系列接口和实现类,在组织框架代码时,使用了一系列优秀的设计模式,使代码无论在性能上还是API操作上得到很大提升.优点
:- 减少开发时间、降低开发难度,并且还保证设计质量
- 降低程序员之间沟通以及日后维护的成本
总结
:- 总之,框架是一个半成品,已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度
1.2 认识ORM
传统 JDBC 的缺点
:- 需要手动的完成面向对象的Java语言、面向关系的数据库之间数据的转换,代码繁琐无技术含量,影响了开发效率
- 比如查询需要手动的将结果集的列数据转换为Java对象的属性;而添加操作时需要手动将Java对象的属性转换为数据库表的列字段
ORM,Object-Relationl Mapping
,对象关系映射,它的作用是在关系型数据库和对象之间作一个映射优点
- 操作数据库的时候,只要像平时操作对象一样操作它,ORM框架会根据映射完成对数据库的操作,不需要写复杂的SQL语句
1.2.1 持久化
持久(Persistence)
,即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)持久化的主要应用
- 是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等
1.2.2 持久层
持久层(Persistence Layer)
,即专注于实现数据持久化应用领域
的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联
- 之前使用JDBC访问数据库的DAO层,后面采用MyBatis访问数据库的mapper层,就是持久层
1.3 认识 MyBatis
MyBatis
是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射
MyBatis
避免了几乎所有的JDBC
代码和手动设置
参数以及获取结果集MyBatis
可以使用简单的XML
或注解
来配置和映射原生信息,将接口和 Java 的POJOs(Plain Ordinary Java Object,普通的 Java对象)
映射成数据库中的记录
2 MyBatis 快速入门
2.1 项目结构
2.2 pom 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.msb</groupId>
<artifactId>mybatisTest01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--mysqlConnector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--mybatis 核心jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
2.3 准备实体类与数据库
数据库导入即可
实体类:
package top.mikevane.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Author: mikevane
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private String loc;
}
2.4 添加映射文件
在 resource 目录下 创建 top/mikevane 目录,然后添加DeptMapper.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="aaa">
<!--public List<Dept> findAll(){ }-->
<select id="findAll" resultType="com.msb.pojo.Dept" >
select * from dept
</select>
</mapper>
resources目录下准备sqlMapConfig.xml 核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--加载mapper映射文件-->
<mappers>
<mapper resource="com/msb/mapper/DeptMapper.xml"/>
</mappers>
</configuration>
2.5 测试代码
- 在 test 目录下 创建 top/mikevane 目录并新建 Test1 类
package top.mikevane.test;
import top.mikevane.pojo.Dept;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Test1 {
private SqlSession sqlSession;
@Before
public void init(){
SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory factory=ssfb.build(resourceAsStream) ;
sqlSession=factory.openSession();
}
@Test
public void testFindAll(){
// 调用SQL语句
List<Dept> list = sqlSession.selectList("findAll");
for (Dept dept : list) {
System.out.println(dept);
}
}
@After
public void release(){
// 关闭SQLSession
sqlSession.close();
}
}
3 MyBatis 配置详解
3.1 log4j1 和log4j2的使用
导入依赖
<!--log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<!--log4j1 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 可以在MyBatis的配置文件中 配置选择使用的具体的日志实现
<settings>
<!--指定mybatis日志方式,如果不指定,自动查找处理-->
<setting name="logImpl" value="LOG4J"/>
</settings>
3.1.1 log4j 配置文件
在网上可以找到对应的配置文件
将配置文件置于
resource
的根目录即可log4j1 配置文件(properties文件)
log4j.rootLogger=debug,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=d:/msb.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
log4j2 配置文件(xml文件)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
</Console>
<RollingFile name="RollingFile" filename="log/test.log"
filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
<AppenderRef ref="RollingFile" />
</Root>
</Loggers>
</Configuration>
log4j 的日志等级级别
级别 | 描述 |
---|---|
ALL | 所有级别包括自定义级别。 |
DEBUG | 调试消息日志。 |
ERROR | 错误消息日志,应用程序可以继续运行。 |
FATAL | 严重错误消息日志,必须中止运行应用程序。 |
INFO | 信息消息。 |
OFF | 最高可能的排名,旨在关闭日志记录。 |
TRACE | 高于DEBUG。 |
WARN | 用于警告消息。 |
3.2 事务配置(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
3.3 映射文件的加载方式(mappers)
- 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件
- mapper映射文件的文件路径导入 使用的mapper标签的resource属性
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
- 网络资源路径 使用的mapper标签的url属性
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
- 接口的全限定名导入 使用的是mapper标签的class属性 (基于接口的代理模式开发)
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
- 包扫描形式加载所有的mapper映射文件 使用的是 package标签
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
3.4 实体类别名(typeAliases)
- 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<!--包扫描起别名 类的短路径名首字母小写-->
<typeAlias alias="dept" type="top.mikevane.pojo.Dept"></typeAlias>
<typeAlias alias="emp" type="top.mikevane.pojo.Emp"></typeAlias>
</typeAliases>
这样 当这样配置时,
dept
可以用在任何使用top.mikevane.pojo.Emp
的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<!--包扫描起别名 类的短路径名首字母小写-->
<package name="top.mikevane.pojo"/>
</typeAliases>
每一个在包
top.mikevane.pojo
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如top.mikevane.pojo.Dept
的别名为dept
若有注解,则别名为其注解值。见下面的例子:
@Alias("dept")
public class Dept
...
}
3.4.1 常见的 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
3.5 环境配置
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。
- 例如,开发、测试和生产环境需要有不同的配置;
- 或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射
- 还有许多类似的使用场景。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
3.5.1 SqlSessionFactory 实例
每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
- 如果忽略了环境参数,那么将会加载默认环境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
3.5.2 引入 properties 属性来导入配置文件
- 在
resource
目录下准备jdbc.properties
属性配置文件,内容如下:
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=123456
在
MyBatis
配置文件中引入jdbc.properties
配置文件
4. MyBatis 普通模式开发
- 普通模式,也称为传统DAO模式,就是在传统DAO模式下,定义接口和实现类,如 interface EmpDao class EmpDaoImpl implements EmpDao.
- 在实现类中,用SQLSession对象调用 select insert delete update 等方法实现.目前极为少见.
- 在传统模式下,我们需要知道SqlSession对象 实现CURD和 参数传递的处理
4.1 MyBatis 查询的三种方式
SqlSession对象本身的API中就有三个查询方法,分别能够实现如下查询方式
返回单个对象 selectOne
<select id="findOne" resultType="emp" >
select * from emp where empno = 7499
</select>
resultType
相当于返回值类型paramaterType
参数类型测试代码
:
@Test
public void testSelectOne(){
// 查询单个对象
System.out.println("sqlSession查询单个对象");
Emp emp = sqlSession.selectOne("selectOne");
System.out.println(emp);
}
返回对象List集合 selectList
<select id="findAll" resultType="emp">
select * from emp
</select>
测试代码
:
@Test
public void testSelectList(){
// 查询多个对象的List集合
System.out.println("sqlSession查询对象List集合");
List<Emp> emps = sqlSession.selectList("EmpMapper.selectAll");
emps.forEach(System.out::println);
}
返回对象Map集合 selectMap
<select id="findEmpMap" resultType="map">
select * from emp
</select>
测试代码
:
@Test
public void testSelectMap(){
// 查询多个对象的Map集合
System.out.println("sqlSession查询对象Map集合");
Map<Integer, Emp> empMap = sqlSession.selectMap("selectAllMap", "EMPNO");
Set<Integer> empnos = empMap.keySet();
for (Integer empno : empnos) {
System.out.println(empno+" :" +empMap.get(empno));
}
}
sqlSession.selectMap
传入的参数:第一个为方法名,第二个参数为主键名
4.2 MyBatis 参数传递的三种方式
- MyBatis 在SQL语句上可以使用${} #{} 代表参数的占位
- 如果参数是单个基本数据类型,{}中名字可以随便写,最好
见名知意
${}
:代表 mybatis 底层使用Statment语句对象
,参数是以字符串拼接
的形式设置#{}
:代表mybatis底层使用的preparedStatment
语句对象,参数使用?作为占位符处理
- 如果参数是单个基本数据类型,{}中名字可以随便写,最好
- 单个基础数据类型作为参数
<!--
参数为一个基本数据类型
根据员工工号查询员工的全部信息,返回单个员工对象
public Emp findByEmpno(int empno);
parameterType 在有参数情况下也是可以省略不写 mybatis 可以根据实际情况自动判断
如果要写parameterType 那么就要与实际参数的类型相匹配
-->
<select id="findByEmpno" resultType="emp" parameterType="int">
select * from emp where empno = #{empno}
</select>
- 多个基础数据类型的map 集合作为参数
传入参数设置
:
public void testMapArg(){
// 测试Map集合作为参数
Map<String,Object> args=new HashMap<>();
args.put("deptno", 20);
args.put("sal", 3000.0);
List<Emp> emps = sqlSession.selectList("findEmpByDeptnoAndSal", args);
emps.forEach(System.out::println);
}
接受参数设置
:
<select id="findEmpByDeptnoAndSal" resultType="emp" parameterType="map">
select * from emp where deptno = #{deptno} and sal >= #{sal}
</select>
#{}中写的是map集合中,参数的键
< > 推荐进行转译处理,参照HTML转译
- 引用类型作为参数
<select id="findEmpByDeptnoAndSal2" resultType="emp" parameterType="emp">
select * from emp where deptno = #{deptno} and sal >= #{sal}
</select>
参数是我们自定义的类型,那么 #{}中写的是参数的属性名
4.3 MyBatis 完成 CURD 全部操作
增加
:增删方法的返回值类型都是
int
,resultType
就无需指定了insert update delete
标签中没有resultType
但是仍然可以有
paramaterType
<insert id="addEmp" parameterType="emp">
insert into emp values(#{empno},#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>
修改
:
<update id="updateEmp" parameterType="emp">
update emp set ename = #{ename} where empno=#{empno}
</update>
删除
:
<delete id="deleteEmp" parameterType="int">
delete from emp where empno >= #{empno}
</delete>
5 MyBatis 基于接口代理模式开发
前面操作的缺点
:- 不管是selectList()、selectOne()、selectMap(),都是通过SQLSession对象的API完成增删改查,都 只能提供一个查询参数。如果要多个参数,需要封装到
JavaBean
或者Map
中,并不一定永远是一个好办法 返回值类型
较固定- 只提供了
映射文件
,没有提供数据库操作的接口
,不利于后期的维护扩展
- 不管是selectList()、selectOne()、selectMap(),都是通过SQLSession对象的API完成增删改查,都 只能提供一个查询参数。如果要多个参数,需要封装到
解决办法
:- 在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式,
在实际开发中多使用该方式
- 之后多使用Mapper代理的方式来实现对Emp表的CRUD操作,还包括多个参数传递、模糊查询、自增主键回填等更多的技术实现
- 在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式,
优点
:- 接口与模块更加规范
- 参数处理变得多样,接口中的方法参数列表由程序员自主决定
- 通过
代理模式
由mybatis
提供接口的实现类对象,可以不写实现类了
5.1 使用Mapper代理方式实现查询
项目结构
:Emp 接口
:
public interface EmpMapper {
List<Emp> findAll();
}
EmpMapper.xml映射文件
<mapper namespace="top.mikevane.mapper.EmpMapper">
<select id="findAll" resultType="top.mikevane.pojo.Emp">
select * from emp
</select>
</mapper>
- 在
sqlMapConfig.xml
核心配置文件中使用包扫描
形式加载所有的映射文件
<!--加载mapper映射文件-->
<mappers>
<mapper class="top.mikevane.mapper.EmpMapper"></mapper>
</mappers>
测试类
:
@Test
public void testFindAll(){
EmpMapper empMapper=sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = empMapper.findAll();
emps.forEach(System.out::println);
}
6 代理模式下的参数传递问题
6.1 多参数传递
单个基本数据类型
mapper 接口
:
public interface EmpMapper {
/**
* 根据员工编号查询单个员工信息
* @param empNum 员工编号
* @return 员工信息
*/
Emp selectByEmpNo(int empNum);
}
xml 映射文件
:
<select id="selectByEmpNo" resultType="emp">
select * from emp where empno = #{empno}
</select>
测试代码
:- ```java
@Test
public void selectByEmpNoTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.selectByEmpNo(7902);
System.out.println(emp);
}2. `多个基本数据类型` - `@Param注解 使用场景`: - 方法有 `多个参数`,需要 `@Param` 注解 - 方法参数要 `取别名`,需要 `@Param` 注解 - `XML` 中的 `SQL` 使用了 `$` ,那么参数中也需要 `@Param` 注解 - `动态 SQL` ,如果在动态 SQL 中使用了参数作为变量,那么也需要 `@Param` 注解,即使你只有一个参数 - `接口和映射文件要求`: 1. `接口的名字和Mapper映射` 为文件 `名字` 必须 `保持一致`(不包含拓展名) 2. Mapper映射文件的 `namespace` 必须是 `接口的全路径名` 3. sql语句的 `id` 必须是对应 `方法的名` 4. `DeptMapper映射文件` 应该和 `接口编译之后` 放在 `同一个目录` 下 - `mapper 接口`: ```java /** * 根据员工编号和薪资下限去查询员工信息 * * @param deptno 员工编号 * @param sal 薪资下限 * @return 多个Emp对象的List集合 */ List<Emp> selectByDeptnoAndSal(@Param("deptno") int deptno, @Param("sal") double sal);
- ```java
xml 映射文件
:
<!--
多个基本数据类型作为方法参数
List<Emp> selectByDeptnoAndSal(@Param("deptno") int deptno, @Param("sal") double sal);
方式1 arg* arg0 arg1 arg2 数字是索引,从0开始
方式2 param* param1 param2 param3 数字是编号,从1开始
使用别名
List<Emp> selectByDeptnoAndSal(@Param("deptno") int deptno,@Param("sal") double sal);
通过@Param注解使用别名之后,就不能再使用arg* 但是可以继续使用param*
-->
<select id="selectByDeptnoAndSal" resultType="emp">
<!--select * from emp where empno =#{arg0} and sal >= #{arg1}-->
<!-- select * from emp where empno =#{param1} and sal >= #{param2}-->
select * from emp where deptno =#{deptno} and sal >= #{sal}
</select>
测试代码
:
@Test
public void selectByEmpNumAndSalTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps2 = mapper.selectByDeptnoAndSal(10, 1500);
emps2.forEach(System.out::println);
}
单个引用数据类型
mapper 接口
:
List<Emp> selectByDeptnoAndSal2(Emp emp);
xml 映射文件
:
<!--单个引用类型,{}中写的使用对象的属性名-->
<select id="selectByDeptnoAndSal2" resultType="top.mikevane.pojo.Emp" parameterType="emp">
select *
from emp
where deptno = #{deptno}
and sal >= #{sal}
</select>
测试代码
:
@Test
public void selectByEmpNumAndSalTest2(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setDeptno(10);
emp.setSal(1500.00);
List<Emp> emps = mapper.selectByDeptnoAndSal2(emp);
emps.forEach(System.out::println);
}
map集合数据类型
mapper 接口
:
List<Emp> selectByDeptnoAndSal3(Map<String,Object> map);
xml 映射文件
:
<!--
参数是map,{}写键的名字
-->
<select id="selectByDeptnoAndSal3" resultType="top.mikevane.pojo.Emp" parameterType="map">
<!--select * from emp where deptno =#{arg0} and sal >= #{arg1}-->
<!-- select * from emp where deptno =#{param1} and sal >= #{param2}-->
select * from emp where deptno =#{deptno} and sal >= #{sal}
</select>
测试代码
:
@Test
public void selectByEmpNumAndSalTest4(){
EmpMapper empMapper=sqlSession.getMapper(EmpMapper.class);
Map<String,Object> map=new HashMap<>();
map.put("deptno", 20);
map.put("sal", 3000.0);
List<Emp> emps = empMapper.selectByDeptnoAndSal3(map);
emps.forEach(System.out::println);
}
多个引用数据类型
mapper 接口
:
List<Emp> selectByDeptnoAndSal4(@Param("empa") Emp empa,@Param("empb") Emp empb);
xml 映射文件
:
<!--
多个引用类型作为方法参数
List<Emp> selectByDeptnoAndSal4(@Param("empa") Emp empa,@Param("empb") Emp empb);
如果用@Param定义了别名,那么就不能使用arg*.属性名,但是可以使用param*.属性名和别名.属性名
-->
<select id="selectByDeptnoAndSal4" resultType="top.mikevane.pojo.Emp">
<!-- select * from emp where deptno =#{arg0.deptno} and sal >= #{arg1.sal} -->
select * from emp where deptno =#{param1.deptno} and sal >= #{param2.sal}
<!-- select * from emp where deptno =#{empa.deptno} and sal >= #{empb.sal}-->
</select>
测试代码
:
@Test
public void selectByEmpNumAndSalTest4() {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
Emp condition1 = new Emp();
condition1.setDeptno(10);
Emp condition2 = new Emp();
condition2.setSal(1500.0);
List<Emp> emps4 = empMapper.selectByDeptnoAndSal4(condition1, condition2);
for (Emp em : emps4) {
System.out.println(em);
}
}
6.2 模糊查询
- 在进行模糊查询时,在映射文件中可以使用concat()函数来连接参数和通配符。另外注意对于特殊字符,比如<,不能直接书写,应该使用字符实体替换
mapper 接口
/**
* 根据员工名字模糊匹配多个员工信息
* @param ename 员工名字片段
* @return 多个员工对象集合
*/
List<Emp> findByEname(String ename);
xml 映射文件
<select id="findByEname" resultType="top.mikevane.pojo.Emp">
SELECT * FROM emp WHERE ename like concat('%',#{ename},'%')
</select>
测试代码
@Test
public void findByEnameTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> a = mapper.findByEname("a");
a.forEach(System.out::println);
}
6.3 自增主键回填
- MySQL支持主键自增。有时候完成添加后需要立刻
获取刚刚自增的主键
,由下一个操作来使用。比如结算构造车后,主订单的主键确定后,需要作为后续订单明细项的外键存在。如何拿到主键呢,MyBatis提供了支持,可以非常简单的获取。 mapper 接口
/**
* 添加一个dept对象
* @param dept
* @return
*/
int addDept(Dept dept);
int addDept2(Dept dept);
xml 映射文件
<!--
useGeneratedKeys="true" 返回数据库帮我们生成的主键
keyProperty="deptno" 生成的主键值用我们dept对象那个属性存储
-->
<insert id="addDept" parameterType="dept" useGeneratedKeys="true" keyProperty="deptno">
insert into dept values(null,#{dname},#{loc})
</insert>
<insert id="addDept2" parameterType="dept">
<selectKey order="AFTER" keyProperty="deptno" resultType="int">
select @@identity
</selectKey>
insert into dept values(null,#{dname},#{loc})
</insert>
测试代码
@Test
public void addDeptTest(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = new Dept(null,"AI学院","北京");
int i = mapper.addDept(dept);
System.out.println(i);
System.out.println(dept.getDeptno());
sqlSession.commit();
}
@Test
public void addDept2Test(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = new Dept(null,"AI学院","北京");
int i = mapper.addDept2(dept);
System.out.println(i);
System.out.println(dept.getDeptno());
sqlSession.commit();
}
方法一
useGeneratedKeys
:表示要使用自增的主键keyProperty
:表示把自增的主键赋给JavaBean的哪个成员变量。以添加Dept对象为例,添加前Dept对象的deptno是空的,添加完毕后可以通过getDeptno() 获取自增的主键。
方法二
order
:取值AFTER|BEFORE,表示在新增之后|之前执行<selectKey>中的SQL命令keyProperty
:执行select @@identity
后结果填充到哪个属性中resultType
:结果类型
技术扩展
在很多应用场景中需要新增数据后获取到新增数据的主键值,针对这样的需求一般由三种解决方式:
主键自定义,用户通过
UUID或时间戳
等方式生成唯一主键,把这个值当做主键值。在分布式场景
中应用较多。查询后通过
select max(主键) from 表
获取主键最大值
。这种方式在多线程访问
情况下可能出现问题。查询后通过
select @@identity
获取最新生成主键。要求这条SQL必须在insert操作之后
,且数据库连接没有关闭
。
6.4 实现 CURD 操作
mapper 接口
/**
* 增加员工信息
* @param emp 存储新增员工信息的Emp对象
* @return 影响的行数
*/
int addEmp(Emp emp);
/**
* 根据员工编号修改员工的姓名
* @param emp 更新员工信息的Emp对象
* @return
*/
int updateEnameByEmpno(Emp emp);
/**
* 根据员工编号删除对应员工
* @param empno
* @return
*/
int deleteByEmpno(@Param("empno") Integer empno);
xml 映射文件
<insert id="addEmp" parameterType="emp" useGeneratedKeys="true" keyProperty="empno">
insert into emp values(null,#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>
<update id="updateEnameByEmpno" parameterType="emp">
UPDATE emp set ename =#{ename} where empno =#{empno}
</update>
<delete id="deleteByEmpno">
DELETE FROM emp where empno = #{empno}
</delete>
测试代码
@Test
public void addEmpTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp(null, "TOM", "SALESMAN", 7521, new Date(), 2314.0, 100.0, 10);
int i = mapper.addEmp(emp);
System.out.println(i);
System.out.println("新增的数据的主键为: " + emp.getEmpno());
sqlSession.commit();
}
@Test
public void updateEmpTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEmpno(7935);
emp.setEname("MIKE");
mapper.updateEnameByEmpno(emp);
sqlSession.commit();
}
@Test
public void deleteByEmpnoTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
int i = mapper.deleteByEmpno(7935);
System.out.println(i);
sqlSession.commit();
}
7 动态SQL
问题提出
- 经常遇到很多按照很多查询条件进行查询的情况,比如京东根据不同的条件筛选商品。其中经常出现很多条件不取值的情况,在后台应该如何完成最终的SQL语句呢?
- 如果采用JDBC进行处理,需要根据条件是否取值进行SQL语句的拼接,一般情况下是使用StringBuilder类及其append方法实现,还是有些繁琐的
- 如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦
动态SQL优点
- MyBatis 在简化操作方法提出了动态SQL功能,将使用
Java代码拼接
SQL语句,改变为在XML映射文件中截止标签
拼接SQL语句。相比而言,大大减少了代码量,更灵活、高度可配置、利于后期维护。 - MyBatis 中动态SQL是编写在
mapper.xml
中的,其语法和JSTL
类似,但是却是基于强大的OGNL表达式实现
的。 - MyBatis 也
可以在注解中配置SQL
,但是由于注解功能受限
,尤其是对于复杂的SQL语句,可读性很差,所以较少使用
- MyBatis 在简化操作方法提出了动态SQL功能,将使用
7.1 if 标签
设置好需要接收的变量集,根据给定的变量来拼接 if 字符串 (避免出现每一种情况都需要写对应的sql语句)
mapper 接口
/**
* 根据给定的字段查找对应数据
* @param emp 对象中存入字段
* @return
*/
List<Emp> findByCondition(Emp emp);
xml 映射文件
<select id="findByCondition" resultType="top.mikevane.pojo.Emp">
SELECT * FROM emp where 1 = 1
<if test="empno != null">
and empno = #{empno}
</if>
<if test="ename != null">
and ename = #{ename}
</if>
<if test="job != null">
and job = #{job}
</if>
<if test="mgr != null">
and mgr = #{mgr}
</if>
<if test="hiredate != null">
and hiredate = #{hiredate}
</if>
<if test="sal != null">
and sal = #{sal}
</if>
<if test="comm != null">
and comm = #{comm}
</if>
<if test="deptno != null">
and deptno = #{deptno}
</if>
测试代码
@Test
public void findByConditionTest(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
Emp emp = new Emp();
// emp.setEmpno(7934);
emp.setDeptno(20);
List<Emp> condition = mapper.findByCondition(emp);
condition.forEach(System.out::println);
}
7.2 Where 标签
- 用于处理where关键字和and (用于避免出现无参数或其他情况时出现错误)
mapper 接口
/**
* 根据给定的字段查找对应数据
* @param emp 对象中存入字段
* @return
*/
List<Emp> findEmpByCondition(Emp emp);
xml 映射文件
<select id="findEmpByCondition" resultType="top.mikevane.pojo.Emp">
SELECT * FROM emp
<where>
<if test="empno != null">
and empno = #{empno}
</if>
<if test="ename != null">
and ename = #{ename}
</if>
<if test="job != null">
and job = #{job}
</if>
<if test="mgr != null">
and mgr = #{mgr}
</if>
<if test="hiredate != null">
and hiredate = #{hiredate}
</if>
<if test="sal != null">
and sal = #{sal}
</if>
<if test="comm != null">
and comm = #{comm}
</if>
<if test="deptno != null">
and deptno = #{deptno}
</if>
</where>
</select>
测试代码
@Test
public void findEmpByConditionTest(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
Emp emp = new Emp();
emp.setDeptno(20);
List<Emp> empByCondition = mapper.findEmpByCondition(emp);
empByCondition.forEach(System.out::println);
}
7.3 Choose 标签
- 类似于 java 中的 switch 语句
mapper 接口
List<Emp> findEmpByCondition2(Emp emp);
xml 映射文件
<select id="findEmpByCondition2" resultType="top.mikevane.pojo.Emp">
select * from emp
<where>
<choose>
<when test="empno != null">
and empno= #{empno}
</when>
<when test="ename != null and ename != ''">
and ename= #{ename}
</when>
<when test="job != null and job != ''">
and job= #{job}
</when>
<when test="mgr != null ">
and mgr= #{mgr}
</when>
<when test="hiredate != null ">
and hiredate= #{hiredate}
</when>
<when test="sal != null">
and sal= #{sal}
</when>
<when test="comm != null ">
and comm =#{comm}
</when>
<when test="deptno != null ">
and deptno= #{deptno}
</when>
</choose>
</where>
</select>
测试代码
@Test
public void findEmpByCondition2Test(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
Emp emp = new Emp();
emp.setDeptno(20);
emp.setMgr(7698);
List<Emp> empByCondition = mapper.findEmpByCondition2(emp);
empByCondition.forEach(System.out::println);
}
测试结果
Emp(empno=7499, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300.0, deptno=30)
Emp(empno=7521, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500.0, deptno=30)
Emp(empno=7654, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400.0, deptno=30)
Emp(empno=7844, ename=TURNER, job=SALESMAN, mgr=7698, hiredate=Tue Sep 08 00:00:00 CST 1981, sal=1500.0, comm=0.0, deptno=30)
Emp(empno=7900, ename=JAMES, job=CLERK, mgr=7698, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=950.0, comm=0.0, deptno=30)
- 分析可得,此时只查询了 mgr 为7698的值,设置的条件 dept=20 并没有生效,说明 MyBatis 的
choose
语句确实与 Java 中的switch
语句很类似,都是满足了前一个条件后,其他条件就不再进行判断了
7.4 Set 标签
- 在我个人看来,设置
set
标签的理由与if
标签很像:如果有传入的数据有各种情况,那么就不可能针对每一种情况进行书写 SQL,所以就诞生了类似于if/set
标签 mapper 接口
/**
* 根据各种情况更新数据
* @param emp 对象中存取需要修改的数据(比如传入值只有 empno,或者只有 deptno)
* @return
*/
int updateEmpByCondition(Emp emp);
xml 映射文件
<update id="updateEmpByCondition" parameterType="emp">
UPDATE emp
<set>
<if test="ename != null and ename != ''">
, ename =#{ename}
</if>
<if test="job != job and job != ''">
, job =#{job}
</if>
<if test="mgr != null">
, mgr =#{mgr}
</if>
<if test="hiredate != null">
, hiredate =#{hiredate}
</if>
<if test="sal != null">
, sal =#{sal}
</if>
<if test="comm != null">
, comm =#{comm}
</if>
<if test="deptno != null">
, deptno =#{deptno}
</if>
</set>
where empno = #{empno}
</update>
测试代码
@Test
public void updateEmpByCondtionTest(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
Emp emp = new Emp();
emp.setEmpno(7934);
emp.setEname("MIKE");
emp.setSal(6666.0);
mapper.updateEmpByCondition(emp);
sqlSession.commit();
}
测试结果
- 结果正确,可以根据传入参数来进行动态拼接SQL
7.5 Trim 标签
- 对于
Trim标签
来说,set标签
更像是trim
的子集,因为trim标签
不仅可以做到set
的操作,还可以做其他操作 Trim标签的属性
prefix
要增加什么前缀prefixOverrides
要去除什么前缀suffix
要增加什么后缀suffixOverrides
要去除什么后缀
mapper 接口
int updateEmpByCondition2(Emp emp);
xml 映射文件
<update id="updateEmpByCondition2">
update emp
<trim prefix="set" prefixOverrides=",">
<if test="ename != null and ename != ''">
, ename =#{ename}
</if>
<if test="job != job and job != ''">
, job =#{job}
</if>
<if test="mgr != null">
, mgr =#{mgr}
</if>
<if test="hiredate != null">
, hiredate =#{hiredate}
</if>
<if test="sal != null">
, sal =#{sal}
</if>
<if test="comm != null">
, comm =#{comm}
</if>
<if test="deptno != null">
, deptno =#{deptno}
</if>
</trim>
where empno = #{empno}
</update>
测试代码
@Test
public void updateEmpByCondtion2Test(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
Emp emp = new Emp();
emp.setEmpno(7934);
emp.setEname("MIKEVANE");
emp.setSal(1234.0);
mapper.updateEmpByCondition2(emp);
sqlSession.commit();
}
测试结果
- 结果显示修改正确
7.6 Bind标签
一般用于处理
模糊查询
mapper 接口
/**
* 根据传入的字符串进行模糊查询
* @param ename
* @return
*/
List<Emp> findEmpByEname(@Param("name") String ename);
xml 映射文件
<select id="findEmpByEname" resultType="top.mikevane.pojo.Emp">
<bind name="enamePattern" value="'%'+name+'%'"/>
select * from emp where ename like #{enamePattern}
</select>
测试代码
@Test
public void findEmpByEnameTest(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
List<Emp> m = mapper.findEmpByEname("M");
m.forEach(System.out::println);
}
测试结果
Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=0.0, deptno=20)
Emp(empno=7654, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400.0, deptno=30)
Emp(empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=0.0, deptno=20)
Emp(empno=7900, ename=JAMES, job=CLERK, mgr=7698, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=950.0, comm=0.0, deptno=30)
Emp(empno=7934, ename=MIKEVANE, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1234.0, comm=0.0, deptno=10)
- 根据运行结果可知,对
M
进行模糊查询的结果与期望结果一致
7.7 Sql标签
- 在我个人看来,sql标签 与 方法/函数 的作用类似,都是将重复的代码进行封装,减少代码的冗余
- 比如:
- 对某个表进行多次查询
- 多次查询,返回的列都相同的情况
例子
:- 将
7.2 where标签
的例子复用 - 接口与测试代码不变,仅将
where标签
之前的语句修改为 <include refid=”baseSelect”/>
- 将
<select id="findEmpByCondition" resultType="top.mikevane.pojo.Emp">
<include refid="baseSelect"/>
<where>
<if test="empno != null">
and empno = #{empno}
</if>
<if test="ename != null">
and ename = #{ename}
</if>
<if test="job != null">
and job = #{job}
</if>
<if test="mgr != null">
and mgr = #{mgr}
</if>
<if test="hiredate != null">
and hiredate = #{hiredate}
</if>
<if test="sal != null">
and sal = #{sal}
</if>
<if test="comm != null">
and comm = #{comm}
</if>
<if test="deptno != null">
and deptno = #{deptno}
</if>
</where>
</select>
7.8 Foreach 标签
- 对传入数组与集合进行操作
foreach 标签的属性
collection=""
:遍历的集合或者是数组- 参数是数组,collection中名字指定为array
- 参数是List集合,collection中名字指定为list
separator=""
:多个元素取出的时候 用什么文字分隔open=""
:以什么开头close=""
:以什么结尾item=""
:中间变量名
mapper 接口
/**
* 根据传入员工编号数组/列表进行查询
* @param empnos
* @return
*/
List<Emp> findByEmpnos1(int[] empnos);
List<Emp> findByEmpnos2(List empno);
xml 映射文件
<select id="findByEmpnos1" resultType="top.mikevane.pojo.Emp">
<include refid="baseSelect"/>
where empno in
<foreach collection="array" separator="," open="(" close=")" item="empno">
#{empno}
</foreach>
</select>
<select id="findByEmpnos2" resultType="top.mikevane.pojo.Emp">
<include refid="baseSelect"/>
where empno in
<foreach collection="list" separator="," open="(" close=")" item="empno">
#{empno}
</foreach>
</select>
测试代码
@Test
public void findByEmpnos1Test(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
int[] arr = {7934,7900};
List<Emp> byEmpnos1 = mapper.findByEmpnos1(arr);
byEmpnos1.forEach(System.out::println);
}
@Test
public void findByEmpnos2Test(){
EmpMapper2 mapper = sqlSession.getMapper(EmpMapper2.class);
List<Integer> list = new ArrayList<>();
Collections.addAll(list,7934,7900);
List<Emp> byEmpnos1 = mapper.findByEmpnos2(list);
byEmpnos1.forEach(System.out::println);
}
测试结果
Emp(empno=7900, ename=JAMES, job=CLERK, mgr=7698, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=950.0, comm=0.0, deptno=30)
Emp(empno=7934, ename=MIKEVANE, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1234.0, comm=0.0, deptno=10)
8 实现多表查询
- 前面已经使用 MyBatis 完成了对Emp表的CRUD操作,不管是使用 SqlSession 直接操作,还是使用 Mapper 代理方式,都只是完成了对
单个数据库表
的操作。这肯定是远远不够的。 - 在实际开发中,经常会将来自
多张表
的数据在一个位置显示。比如查询并显示的员工信息中会有来自部门表、岗位表的数据,而后台一般是定义一个方法
8.1 关联查询
8.1.1 手动处理映射关系 resultMap
手动处理
数据库查询字段和封装实体类属性
之间的映射关系主键一般使用id属性
当属性名和查询出的数据表字段名相同
可以不写映射关系
mapper 接口
List<Emp> findAll();
xml 映射文件
测试代码
@Test
public void findAllTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> all = mapper.findAll();
all.forEach(System.out::println);
}
8.1.2 各表关系
8.1.3 一对一关联查询 association
association
封装一对一信息关系的标签property
类的属性名javaType
用哪个类的对象给属性赋值
需求:
根据编号查询员工信息及所在的部门信息mapper 接口
/**
* 根据员工编号查询员工的所有信息并携带所在的部门信息
* @param empno 要查询的员工编号
* @return Emp对象,组合了Dept对象作为属性,对部门信息进行存储
*/
Emp findEmpJoinDeptByEmpno(int empno);
xml 映射文件
<resultMap id="empJoinDept" type="top.mikevane.pojo.Emp">
<!--设置emp本身的八个属性的映射关系-->
<id property="empno" column="empno"></id>
<result property="ename" column="ename"></result>
<result property="job" column="job"></result>
<result property="sal" column="sal"></result>
<result property="hiredate" column="hiredate"></result>
<result property="mgr" column="mgr"></result>
<result property="comm" column="comm"></result>
<result property="deptno" column="deptno"></result>
<!-- 封装一对一信息关系 -->
<association property="dept" javaType="top.mikevane.pojo.Dept">
<id property="deptno" column="deptno"></id>
<result property="loc" column="loc"></result>
<result property="dname" column="dname"></result>
</association>
</resultMap>
<select id="findEmpJoinDeptByEmpno" resultMap="empJoinDept">
select * from emp
left join dept
on emp.deptno = dept.deptno
where empno = #{empno}
</select>
测试代码
@Test
public void findEmpJoinDeptByEmpnoTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp deptByEmpno = mapper.findEmpJoinDeptByEmpno(7934);
System.out.println(deptByEmpno);
}
测试结果
Emp(empno=7934, ename=MIKEVANE, job=CLERK, mgr=7782, hiredate=Sun Jan 23 00:00:00 CST 2000, sal=6666.0, comm=null, deptno=10, dept=Dept(deptno=10, dname=ACCOUNTING, loc=NEW YORK))
- 由结果可知,查询到的
dept
数据存入到了emp
对象中
8.1.4 一对多关联查询 collection
collection
封装一对多信息关系的标签ofType
类路径
,用于接收连接
后的数据
需求
:根据部门编号查询部门信息及该部分的所有员工信息mapper 接口
/**
* 根据部门编号查询部门信息及该部分的所有员工信息
* @param deptno 要查询的部门编号
* @return Dept对象,内部组合了一个Emp的List属性用于封装部门的所有员工信息
*/
Dept findDeptJoinEmpsByDeptno(int deptno);
xml 映射文件
<resultMap id="deptJoinEmps" type="top.mikevane.pojo.Dept">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="empList" ofType="top.mikevane.pojo.Emp">
<id property="empno" column="empno"/>
<result property="ename" column="ename"/>
<result property="job" column="job"/>
<result property="sal" column="sal"/>
<result property="hiredate" column="hiredate"/>
<result property="mgr" column="mgr"/>
<result property="comm" column="comm"/>
<result property="deptno" column="deptno"/>
</collection>
</resultMap>
<select id="findDeptJoinEmpsByDeptno" resultMap="deptJoinEmps">
select * from dept d left join emp e on d.deptno = e.deptno
where d.deptno = #{deptno}
</select>
测试代码
@Test
public void findDeptJoinEmpsByDeptnoTest(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept empsByDeptno = mapper.findDeptJoinEmpsByDeptno(20);
System.out.println(empsByDeptno);
System.out.println("连接的数据->");
List<Emp> empList = empsByDeptno.getEmpList();
empList.forEach(System.out::println);
}
测试结果
Dept(deptno=20, dname=RESEARCH, loc=DALLAS, empList=[Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null), Emp(empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20, dept=null), Emp(empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20, dept=null), Emp(empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20, dept=null), Emp(empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20, dept=null)])
连接的数据->
Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null)
Emp(empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20, dept=null)
Emp(empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20, dept=null)
Emp(empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20, dept=null)
Emp(empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20, dept=null)
8.1.5 多对多关联查询
- 运用前面的一对多与一对一的方法实现多对多
需求
:根据项目编号查询一个项目信息及参与该项目的所有员工信息- 项目的具体信息为一个表,项目与参与的员工信息为一个中间表,员工具体信息为一个表
- 利用项目号根据中间表找到对应的员工编号(一对多),然后再根据员工编号找到具体员工信息(一对一)
mapper 接口
/**
* 根据项目编号查询一个项目信息及参与该项目的所有员工信息
* @param pid 项目编号
* @return 所有信息封装的Project对象
*/
Project findProjectJoinEmpsByPid(int pid);
xml 映射文件
<resultMap id="projectJoinEmps" type="top.mikevane.pojo.Project">
<id property="pid" column="pid"/>
<result property="pname" column="pname"/>
<result property="money" column="money"/>
<collection property="projectRecords" ofType="top.mikevane.pojo.ProjectRecord">
<id property="empno" column="empno"/>
<id property="pid" column="pid"/>
<association property="emp" javaType="top.mikevane.pojo.Emp">
<id property="empno" column="empno"/>
<result property="ename" column="ename"/>
<result property="job" column="job"/>
<result property="sal" column="sal"/>
<result property="hiredate" column="hiredate"/>
<result property="mgr" column="mgr"/>
<result property="comm" column="comm"/>
<result property="deptno" column="deptno"/>
</association>
</collection>
</resultMap>
<select id="findProjectJoinEmpsByPid" resultMap="projectJoinEmps">
select * from projects p
left join projectrecord pro
on p.pid = pro.pid
left join emp e
on pro.empno = e.empno
where p.pid = #{pid}
</select>
测试代码
@Test
public void findProjectJoinEmpsByPidTest(){
ProjectMapper mapper = sqlSession.getMapper(ProjectMapper.class);
Project projectJoinEmpsByPid = mapper.findProjectJoinEmpsByPid(2);
System.out.println(projectJoinEmpsByPid);
List<ProjectRecord> projectRecords = projectJoinEmpsByPid.getProjectRecords();
projectRecords.forEach(System.out::println);
}
测试结果
Project(pid=2, pname=学生选课系统, money=100000, projectRecords=[ProjectRecord(empno=7369, pid=2, emp=Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null)), ProjectRecord(empno=7499, pid=2, emp=Emp(empno=7499, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300.0, deptno=30, dept=null)), ProjectRecord(empno=7521, pid=2, emp=Emp(empno=7521, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500.0, deptno=30, dept=null))])
ProjectRecord(empno=7369, pid=2, emp=Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null))
ProjectRecord(empno=7499, pid=2, emp=Emp(empno=7499, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300.0, deptno=30, dept=null))
ProjectRecord(empno=7521, pid=2, emp=Emp(empno=7521, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500.0, deptno=30, dept=null))
8.2 级联查询
- 级联查询,顾名思义,就是利于数据库表间的外键关联关系进行自动的级联查询操作。使用MyBatis实现级联查询,除了
实体类
增加关联属性外,还需要在映射文件
中进行配置 个人理解
:将一个xml比喻为一个类,类中的 select 等标签为类的属性,所以级联查询更像是一个类去调用其他类中的方法,只不过多了映射等操作(映射这些操作也可以理解为类中添加属性)
8.2.1 立即加载
需求
:根据部门编号查询员工信息emp mapper 接口
/**
* 根据部门编号查询所对应的员工编号
* @param deptno
* @return
*/
List<Emp> findEmpsByDeptno(int deptno);
dept mapper 接口
/**
* 根据部门编号查询员工信息
* @param deptno 部门编号
* @return 部门信息对象
*/
Dept findDeptByDeptno(int deptno);
emp xml 映射代码
<select id="findEmpsByDeptno" resultType="top.mikevane.pojo.Emp">
select * from emp where deptno = #{deptno}
</select>
dept xml 映射代码
collection 属性
:select=""
调用的另一个SQL语句javaType=""
实体类的属性数据类型column=""
给另一个SQL语句传入的参数列jdbcType=""
参数对应JDBC的数据类型fetchType=""
加载方式 eager 积极加载 lazy延迟加载
<resultMap id="deptJoinEmps" type="dept">
<id property="deptno" column="deptno"/>
<result property="dname" column="dname"/>
<result property="loc" column="loc"/>
<collection property="empList"
select="top.mikevane.mapper.EmpMapper.findEmpsByDeptno"
column="deptno"
javaType="list"
jdbcType="INTEGER"
fetchType="eager"/>
</resultMap>
<select id="findDeptByDeptno" resultMap="deptJoinEmps">
select * from dept where deptno = #{deptno}
</select>
测试代码
@Test
public void findDeptByDeptnoTest(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept deptByDeptno = mapper.findDeptByDeptno(20);
System.out.println(deptByDeptno + "\n");
List<Emp> empList = deptByDeptno.getEmpList();
empList.forEach(System.out::println);
}
测试结果
Dept(deptno=20, dname=RESEARCH, loc=DALLAS, empList=[Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null), Emp(empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20, dept=null), Emp(empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20, dept=null), Emp(empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20, dept=null), Emp(empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20, dept=null)])
Emp(empno=7369, ename=SMITH, job=CLERK, mgr=7902, hiredate=Wed Dec 17 00:00:00 CST 1980, sal=800.0, comm=null, deptno=20, dept=null)
Emp(empno=7566, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20, dept=null)
Emp(empno=7788, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20, dept=null)
Emp(empno=7876, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20, dept=null)
Emp(empno=7902, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20, dept=null)
8.2.2 延迟加载
- 延迟加载,又称按需加载: 延迟加载的内容等到真正使用时才去进行加载(查询)。多用在关联对象或集合中
优点
:- 先单表查询、需要时再从关联表去关联查询,大大降低数据库在单位时间内的查询工作量,将工作在
时间上的分配更加均匀
- 单表要比关联查询多张表
速度要快
- 先单表查询、需要时再从关联表去关联查询,大大降低数据库在单位时间内的查询工作量,将工作在
设置延迟加载
第一步
:打开
全局开关
,在sqlMapConfig.xml
中打开延迟加载的开关。配置完成后所有的association
和collection
元素都生效- ```xml
- `lazyLoadingEnabled`:是否开启延迟加载。是Mybatis是否启用懒加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 `fetchType` 属性来覆盖该项的开关状态 - `aggressiveLazyLoading`:当开启时,任何方法的调用都会懒加载对象的所有属性。否则,每个属性会按需加载, - `第二步`: - 分开关,指定的 `association和collection` 元素中配置 `fetchType` 属性 - ```xml <collection property="empList" select="top.mikevane.mapper.EmpMapper.findEmpsByDeptno" column="deptno" javaType="list" jdbcType="INTEGER" fetchType="lazy" />
- ```xml
eager:表示立刻加载;lazy:表示延迟加载。
将覆盖全局延迟设置
8.3 多表查询总结与拓展
resultMap中的常见属性
属性 | 描述 |
---|---|
property | 需要映射到JavaBean 的属性名称。 |
javaType | property的类型,一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean,那MyBatis 通常会自行检测到。 |
column | 数据表的列名或者列别名。 |
jdbcType | column在数据库表中的类型。这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。 |
typeHandler | 使用这个属性可以覆写类型处理器,实现javaType、jdbcType之间的相互转换。一般可以省略,会探测到使用的什么类型的typeHandler进行处理 |
fetchType | 自动延迟加载 |
select | association、collection的属性,使用哪个查询查询属性的值,要求指定namespace+id的全名称 |
ofType | collection的属性,指明集合中元素的类型(即泛型类型) |
级联查询和多表查询的比较及其选择
级联查询 | 多表查询 | |
---|---|---|
SQL语句数量 | 多条 | 一条 |
性能 | 性能低 | 性能高 |
延迟加载 | 立即加载、延迟加载 | 只有立即加载 |
灵活性 | 更灵活 | 不灵活 |
SQL****难易度 | 简单 | 复杂 |
选择依据 | 简单、灵活 | 高性能 |
ResultType和ResultMap使用场景
- 如果你做的是单表的查询并且封装的实体和数据库的字段一一对应 resultType
- 如果实体封装的属性和数据库的字段不一致 resultMap
- 使用N+1级联查询的时候 resultMap
- 使用的是多表的连接查询 resultMap
一对一关联映射的实现
- 实例:学生和学生证、雇员和工牌
- 数据库层次:主键关联或者外键关联(参看之前内容)
- MyBatis层次:在映射文件的设置双方均使用association即可,用法相同
多对多映射的实现
- 实例:学生和课程、用户和角色
- 数据库层次:引入一个中间表将一个多对多转为两个一对多
- MyBatis层次
- 方法1:在映射文件的设置双方均使用collection即可,不用引入中间类
- 方法2:引入中间类和中间类的映射文件,按照两个一对多处理
自关联映射
- 实例:Emp表中的员工和上级。一般是一对多关联
- 数据库层次:外键参考当前表的主键(比如mgr参考empno)
- MyBatis层次:按照一对多处理,但是增加的属性都写到一个实体类中,增加的映射也都写到一个映射文件中
9 MyBatis 注解开发
使用场景
- 使用Auto Mapping时使用注解非常简单,不需要频繁的在接口和mapper.xml两个文件之间进行切换
- 但是必须配置resultMap时使用注解将会变得很麻烦,这种情况下推荐使用mapper.xml进行配置
- MyBatis支持纯注解方式,支持纯mapper.xml方式,也支持注解和mapper.xml混合形式。当只有接口没有mapper.xml时在mybatis.cfg.xml中可以通过<mapper class=””></mapper>加载接口类。如果是混合使用时,使用<package name=””/>。此方式一直是官方推荐方式。
- 如果某个功能同时使用两种方式进行配置,XML方式将覆盖注解方式
9.1 使用注解完成 CRUD
mapper 接口
@Select("select * from dept where deptno =#{deptno}")
Dept findByDeptno(int deptno);
@Update("update dept set dname =#{dname}, loc =#{loc} where deptno =#{deptno}")
int updateDept(Dept dept);
@Insert("insert into dept values(DEFAULT,#{dname},#{loc})")
int addDept(Dept dept);
@Delete("delete from dept where deptno =#{deptno}")
int removeDept(int deptno);
测试代码
@Test
public void findByDeptnoTest(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.findByDeptno(20);
System.out.println(dept);
}
@Test
public void addDeptTest(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
mapper.addDept(new Dept(null,"北京","天安门"));
sqlSession.commit();
}
@Test
public void updateDept(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
mapper.updateDept(new Dept(41,"重庆","南岸"));
sqlSession.commit();
}
@Test
public void removeDept(){
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
mapper.removeDept(41);
sqlSession.commit();
}
9.2 注解和XML的优缺点
XML | 注解 | |
---|---|---|
优点 | 1.类和类之间的解耦 2.利于修改。直接修改XML文件,无需到源代码中修改。 3.配置集中在XML中,对象间关系一目了然,利于快速了解项目和维护 4.容易和其他系统进行数据交交换 |
1.简化配置 2.使用起来直观且容易,提升开发效率 3.类型安全,编译器进行校验,不用等到运行期才会发现错误。 4.注解的解析可以不依赖于第三方库,可以直接使用Java自带的反射 |
10 缓存
概念
:缓存是一种临时存储少量数据至内存或者是磁盘的一种技术.,它可以减少数据的加载次数,降低工作量,提高程序响应速度- MyBatis允许使用缓存,缓存一般放置在高速读/写的存储器上,比如服务器的内存,能够有效的提供系统性能
- MyBatis分为
一级缓存和二级缓存
,同时也可配置关于缓存设置
一级缓存与二级缓存
:- 一级存储是SqlSession上的缓存
- 二级缓存是在SqlSessionFactory(namespace)上的缓存
- 默认情况下,MyBatis开启一级缓存,没有开启二级缓存。当数据量大的时候可以借助一些第三方缓存框架或Redis缓存来协助保存Mybatis的二级缓存数据
10.1 一级缓存
- 一级存储是SqlSession上的缓存,默认开启,是一种内存型缓存,不要求实体类对象实现Serializable接口
缓存中的数据使用键值对形式存储数据namespace+sqlid+args+offset>>> hash值作为键, 查询出的结果作为值
注意
:当中间发生了增删改
或者SqlSession
调用了commit
,会自动清空缓存
10.1.1 证明一级缓存是SqlSession上的缓存
- 首先,设置一个简单的查询:根据员工编号查询员工信息
@Select("select * from emp where empno = #{empno}")
Emp selectByEmpno(int empno);
- 通过
mapper
执行两次查询操作,并判断两次查询得到的对象是否为同一对象
@Test
public void selectByEmpnoTest(){
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp1 = mapper.selectByEmpno(7934);
Emp emp2 = mapper.selectByEmpno(7934);
System.out.println(emp1 == emp2);
}
- 输出结果为:
true
,说明两次查询到的对象为同一对象,但两次查询的操作都是同一个 mapper
调用,所以还需要证明不同 mapper 查询到的对象仍为同一对象
@Test
public void selectByEmpnoTest(){
EmpMapper mapper1 = sqlSession.getMapper(EmpMapper.class);
EmpMapper mapper2 = sqlSession.getMapper(EmpMapper.class);
Emp emp1 = mapper1.selectByEmpno(7934);
Emp emp2 = mapper1.selectByEmpno(7934);
System.out.println(mapper1 == mapper2);
System.out.println(emp1 == emp2);
}
- 输出结果为:
false true
,证明了不同 mapper 调用同一查询方法,获取到的对象是同一对象
- 现在只是证明了
mapper
与查询到的结果无关,不能说明一级缓存是SqlSession上的缓存,所以对SqlSession
进行证明
@Test
public void selectByEmpnoTest(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
Emp emp1 = mapper1.selectByEmpno(7934);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
Emp emp2 = mapper2.selectByEmpno(7934);
System.out.println(mapper1 == mapper2);
System.out.println(emp1 == emp2);
}
- 输出结果为:
false false
,说明分别获取了SqlSession
后再进行同样的操作后,获取到的emp对象是不同的
,这证明了一级缓存是SqlSession上的缓存
10.2 二级缓存
二级缓存是以
namespace
为标记的缓存,可以是由一个SqlSessionFactory
创建的SqlSession
之间共享缓存数据默认不开启
- 要求实体类必须实现
序列化接口
开启二级缓存
全局开关
:在sqlMapConfig.xml
文件中的<settings>标签配置开启二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
分开关
:在要开启二级缓存的mapper文件中开启缓存
<mapper namespace="com.msb.mapper.Emp">
<cache/>
</mapper>
序列化对象
:二级缓存未必完全使用内存,有可能占用硬盘存储,缓存中存储的JavaBean对象必须实现序列化接口
public class Emp implements Serializable { }
注意:在
commit()或者close()
的时候数据才会被放入到二级缓存
示例
:使用二级缓存实现通过员工编号查询员工信息的功能mapper 接口
Emp selectByEmpno(int empno);
xml 映射文件
<select id="selectByEmpno" resultType="top.mikevane.pojo.Emp" useCache="true" flushCache="false">
select * from emp where empno = #{empno}
</select>
测试代码
@Test
public void selectByEmpnoTest(){
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
Emp emp1 = mapper1.selectByEmpno(7934);
System.out.println(emp1);
//提交sqlSession,将结果放入二级缓存
sqlSession1.commit();
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
Emp emp2 = mapper2.selectByEmpno(7934);
System.out.println(emp2);
}
测试结果
- 可以发现,只查询了一次数据库,说明实现了二级缓存
注意
:MyBatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要对
JavaBean对象实现序列化接口
二级缓存是以
namespace 为单位
的,不同 namespace 下的操作互不影响加入Cache元素后,会对相应命名空间所有的
select
元素查询结果进行缓存,而其中的insert、update、delete 在操作是会清空整个namespace的缓存
cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking
属性 含义 默认值 type
自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口 null readOnly
是否只读
true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全false eviction
缓存策略 LRU(默认)
– 最近最少使用:移除最长时间不被使用的对象FIFO
– 先进先出:按对象进入缓存的顺序来移除它们SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象LRU flushInterval
刷新间隔,毫秒为单位
默认为null,也就是没有刷新间隔,只有执行update、insert、delete语句才会刷新null size
缓存对象个数 1024 blocking
是否使用 阻塞性缓存BlockingCache
true:在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,保证只有一个线程到数据库中查找指定key对应的数据
false:不使用阻塞性缓存,性能更好false
如果在加入Cache元素的前提下让个别select 元素不使用缓存,可以使用useCache属性,设置为false
useCache
控制当前sql语句是否启用缓存flushCache
控制当前sql执行一次后是否刷新缓存
10.3 三方缓存
分布式缓存框架
:我们系统为了提高系统并发 和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache缓存框架Ehcache
:是一种广泛使用的开源java分布式缓存。主要面向通用缓存,javaEE 和 轻量级容器。它具有内存和磁盘存储功能。被用于大型复杂分布式web application的- 这里的三方缓存是作为二级缓存使用的
配置步骤
- 导入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.1</version>
</dependency>
- 在sql映射文件里,开启二级缓存,并把缓存类型指定为EhcacheCache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- 在资源目录下放置一个缓存配置文件,文件名为: ehcache.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true">
<diskStore path="D:\msb\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
Cache配置
name
:Cache的唯一标识maxElementsInMemory
:内存中最大缓存对象数。maxElementsOnDisk
:磁盘中最大缓存对象数,若是0表示无穷大。eternal
:Element是否永久有效,一但设置了,timeout将不起作用。overflowToDisk
:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。timeToIdleSeconds
:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。timeToLiveSeconds
:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。diskExpiryThreadIntervalSeconds
:磁盘失效线程运行时间间隔,默认是120秒。diskSpoolBufferSizeMB
:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。memoryStoreEvictionPolicy
:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
11 逆向工程
逆向工程
:可以针对单表自动生成
MyBatis执行所需要的代码(包括mapper.xml,mapper.java,pojo)- 一般在开发中,常用的逆向工程方式是通过
数据库的表生成代码
- 一般在开发中,常用的逆向工程方式是通过
开发步骤
- 创建maven项目
导入逆向工程依赖
<dependencies>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 日志包,方便查看执行信息-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<!-- 代码生成工具jar -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
配置逆向工程配置文件
:在resources目录下放置一个名为generatorConfig.xml的配置文件,文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<!-- <jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="123">
</jdbcConnection> -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
userId="root"
password="123456">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="top.mikevane.pojo"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="top.mikevane.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="top.mikevane.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="emp" domainObjectName="Emp"
enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
enableSelectByExample="false" selectByExampleQueryId="false" >
<columnOverride column="id" javaType="Integer" />
</table>
</context>
</generatorConfiguration>
- 配置log4j(检查是否运行成功)
- 运行逆向工程代码
package top.mikevane.generator;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class GeneratorSqlmap {
public void generator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("D:\\Learning\\Coding\\java\\ssm\\mabatis\\target\\classes\\generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 生成文件后,根据需要使用
12 main目录下XML文件编译问题
- 有时候,可能会选择将
配置文件或其他文件
与java代码
放置到同一个目录
下,这时候maven不会编译src目录
下非java的文件,需要配置相关属性才可以编译相应后缀的文件 - 在
pom.xml
中配置以下信息
<build>
<!--告诉maven将项目源码中的xml文件也进行编译,并放到编译目录中-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
- 这样maven就可以编译
src目录
下的指定文件了