环境:
- JDK 1.8
- MySQL 5.7
- Maven 3.6.1
- IDEA
预备知识:
- JDBC
- MySQL
- Java基础
- Maven
- Junit
简介
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架
它支持自定义 SQL、存储过程以及高级映射
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis
2013年11月迁移到Github
如何获得MyBatis?
-
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(JDBC),IO文件持久化
- 生活:冷藏,罐头
为什么需要持久化?
- 内存太贵
- 有一些数据要长久保存
持久层
DAO层、Service层、Controller层
- 完成持久化工作的代码块
- 界限分明
为什么需要MyBatis?
- 帮助程序员将数据存入到数据库中
- 方便
- 传统的JDBC代码太复杂了。简化。框架。自动化
- 不用MyBatis也可以。更容易上手
- 优点:
- 简单易学
- 灵活
- 解除sql与程序代码的耦合
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
- (使用的人多)
第一个MyBatis程序
思路:搭建环境 -> 导入MyBatis -> 编写代码 -> 测试。(有疑问可查看MyBatis的中文文档)
搭建环境
创建数据库、创建Maven父工程。
1 |
create database mypos; #创建数据库mypos |
新建项目
- 新建一个普通的maven项目,作为父工程
- 删除src目录
- 导入maven依赖
1 |
<!-- 导入依赖--> |
创建一个模块
在父工程中创建一个模块Module。开始编写Mybatis代码。
核心配置文件
编写mybatis的核心配置文件。在resources文件夹下新建配置文件mybatis-config.xml,添加如下配置:
1 |
|
&
是”&”的转义字符。
解决中文乱码、时区问题,参考这篇博客。
mybatis工具类
编写mybatis工具类,用于获取SqlSession对象。
1 |
package com.qsdbl.utils; |
编写代码
实体类。对应数据库中的一张表。
Mapper接口(对应使用JDBC时的DAO模块)
1
2
3
4
5
6
7package com.qsdbl.mapper;
import com.qsdbl.bean.User;
import java.util.List;
public interface UserMapper {
List<User> getUserList();
}接口实现类由原来的UserMapper实现类转变为一个Mapper配置文件(XML),在该xml文件中只需简单配置sql语句即可。还需要到核心配置文件中进行注册。
Mapper配置文件:namespace,绑定Mapper接口。包名,使用”.”隔开。
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--namespace,绑定一个对应的Dao/Mapper接口(注意包名不要写错)-->
<mapper namespace="com.qsdbl.mapper.UserMapper">
<!-- select查询语句。id对应接口中的方法名。resultType声明返回的数据类型(这里写实体类User,而不是getUserList方法返回的List集合-->
<select id="getUserList" resultType="com.qsdbl.bean.User">
select * from user
</select>
</mapper>核心配置文件mybatis-config.xml中进行注册:
1
2
3
4
5
6<!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册。mappers标签与environments标签同级-->
<mappers>
<mapper resource="com/qsdbl/mapper/UserMapper.xml"/>
<!-- 多个配置文件,可以使用通配符 -->
<!--<mapper resource="com/qsdbl/mapper/*Mapper.xml"/>-->
</mappers>本例子中,Mapper配置文件放在与Mapper接口同一包内,所以路径(注意,路径使用“/”分隔)为
resource="com/qsdbl/mapper/UserMapper.xml"
,但是需要配置Maven才能正常读取到该文件(才会被一块打包出来),建议放到resources文件夹下,路径可直接使用文件名。建议配置文件和接口同名放在同一包下,使用包扫描自动注册。不需要每个配置文件都生动注册。
测试-使用
junit测试
junit测试,SqlSession对象的使用:
1 |
package com.qsdbl.mapper; |
测试结果:
1 |
遍历userList: |
可能遇到的问题
- 配置文件没有注册
- 绑定接口错误(Mapper配置文件中的namespace)
- 方法名不对(Mapper配置文件中的id)
- 返回类型不对(Mapper配置文件中的resultType)
- Maven导出资源问题
配置文件没有注册
1 |
org.apache.ibatis.binding.BindingException: Type interface com.qsdbl.dao.UserDao is not known to the MapperRegistry. |
MapperRegistry异常,写好Mapper配置文件后还需要在核心配置文件mybatis-config.xml中进行注册(mappers标签与environments标签同级)。
1 |
<!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册。mappers标签与environments标签同级--> |
Maven导出资源问题
资源(java文件夹下的xml文件)加载失败的问题。前边UserMapper接口对应的Mapper配置文件由于写在接口同级的包内,所以在打包时可能不会被Maven打包出来,需要在父工程(或当前模块module)的pom.xml文件中添加如下配置(build标签与dependencies标签同级。解决方法参考这篇博客):
1 |
<build> |
写在resources文件夹下则不会出现该问题,缺点就是没有与UserMapper接口放在一起看起来清晰明了,不然文件多了会很乱(不过也可以模仿包名创建对应的文件夹)。
若放在resources文件夹下,在核心配置文件中注册使用的路径如下:
1 |
<mappers> |
属性名/列名冲突
属性名与字段名(数据库列名)不一致。需在核心配置文件中启用一个设置。例如:字段名为“_“连接,属性名为驼峰命名。create_time与createTime。
1 |
<settings> |
CRUD
增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)
select – 查询
选择,查询语句
id,对应的namespace中的方法名
resultType,sql语句执行的返回值类型
parameterType,参数类型
1 |
//parameterType的使用: |
insert – 增加
- 注意:
- 增、删、改,需要提交事务。前边的工具类中openSession()方法添加参数true,会自动提交事务不需要手动提交(下边第19行)。
- 参数使用引用类型时,Mapper配置文件中的sql语句使用表达式
#{引用类型中的字段名}
绑定参数。
1 |
//1、UserMapper接口中,新增一个方法,有一个参数user: |
扩展:插入数据后,返回id,id是数据库自增字段。(摘自SpringBoot项目)
1
2
3
4 //插入(新增)账户
"INSERT INTO account(username,password,sex,birthday,tel,mail,icon)VALUES(#{username},#{password},#{sex},#{birthday},#{tel},#{mail},#{icon})")//el表达式 (
true, keyProperty = "id")//插入数据后,将产生的id放到account对象中 (useGeneratedKeys =
public void insert(Account account);
update – 修改
1 |
//1、UserMapper接口中,新增一个方法,有一个参数user: |
delete – 删除
1 |
//1、UserMapper接口中,新增一个方法,有一个参数name: |
Map传递参数
假设,我们的实体类或者数据库中的表字段或者参数过多,我们可以考虑使用Map而不是一定要使用实体类。
- Map传递参数,直接在sql中取出key即可。
parameterType="Map"
- 对象(实体类)传递参数,直接在sql中取对象的属性即可。
parameterType="类的全限定名"
- 只有一个基本类型参数的情况下,可以直接在sql中取到。
- 多个参数可以考虑使用Map,或者注解。
1 |
//1、UserMapper接口中,新增一个方法,有一个参数map: |
模糊查询
Java代码中拼接通配符%%
1 |
//java代码: |
在sql中使用通配符%%
1 |
//java代码: |
代码:
1 |
//1、UserMapper接口中,新增一个方法,有一个参数value: |
配置解析
核心配置文件
官方建议的核心配置文件名为mybatis-config.xml。
MyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息。
1
2
3
4
5
6
7
8
9
10
11
12
13configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
环境配置
MyBatis可以配置成适应多种环境(environments),但是每个SqlSessionFactory实例只能选择一种环境,所以只适用于在测试时下快速切换环境。
注意一些关键点:
- 默认使用的环境 ID(environments标签。默认:default=”development“。通过设置default对应的环境ID来切换所要使用的环境)。
- 每个 environment 元素定义的环境 ID(environment标签。默认:id=”development“。不同环境,不同的数据库)。
- 事务管理器的配置(environment标签内的transactionManager标签。默认:type=”JDBC“)。
- 在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”)
-
数据源的配置(environment标签内的dataSource标签。默认:type=”POOLED“)。
- 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”)
- UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。
- POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
- JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
属性
我们可以通过properties属性来实现引用配置文件(解耦)。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
编写一个配置文件db.properties,将一些参数写在另一个文件中:
1 |
driver=com.mysql.jdbc.Driver |
在核心配置文件中引入(db.properties放在resources文件夹下,故可以直接通过文件名读取到):
1 |
<!-- 引入外部配置文件--> |
- 可以直接引入外部配置文件。properties标签的resource属性配置外部文件的路径。
- 可以在properties标签内增加一些属性配置。
- 如果存在相同的字段,则优先使用外部配置文件的。
- 注意:properties标签要写在configuration标签的最前边(约定大于配置。在xml文件中,标签是可以规定其顺序的)。
别名
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
方式一:
直接给某个类(全限定名)起别名。
1 |
<!-- 可以给实体类 起别名--> |
方式二:
指定一个包名,MyBatis会在包名下搜索需要的Javabean,默认的别名为这个类 类名首字母小写(实测大写也行)。
1 |
<!-- 可以给实体类 起别名--> |
在实体类比较少的时候,使用第一种方式。可以自定义别名。
在实体类比较多的时候,使用第二种方式。不可以自定义别名,但是可以通过注解实现。
- 在实体类上使用该注解:
@Alias("别名")
- 在实体类上使用该注解:
注意:typeAliases标签要写在properties标签的后边(约定大于配置)。
设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态。
|
true | false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
1 |
<settings> |
其他
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
映射器
映射器的作用是告诉 MyBatis
到哪里去找到定义好的SQL语句。可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的
URL),或类名和包名等。官方文档中有4种方式:
1 |
<!-- 使用相对于类路径的资源引用。放在resources文件夹下,可以直接使用文件名。下边例子中,xml配置文件放在与mapper接口同一包下。推荐该方式 --> |
生命周期和作用域
作用域和生命周期是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
- 最佳作用域是方法作用域。局部方法变量
SqlSessionFactory :
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 最佳作用域是应用作用域。单例模式或者静态单例模式
- 可以理解为数据库连接池
SqlSession:
- 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 可以理解为连接到连接池的一个请求,使用完后需要关闭,否则资源被占用。
这里面的每一个Mapper就代表一个具体的业务。
结果集映射
- 结果集映射可以解决属性名与字段名不一致的问题。实体类中的属性名与数据表中的字段名。
- 可以对复杂的查询结果(一对多查询)进行结果集映射。例如:查询某个教师信息同时查询所教的班级。所教的班级有多个,可以使用一个List集合保存(教师对象中添加一个List变量)。将一个个班级信息保存到教师对象的List变量中就需要使用结果集映射来解决。案例见这篇博客。
例子(属性名字段名不一致):数据库中,字段名为pwd。实体类中属性名写成了password。
思路一:修改sql,起别名,例如:select id,pwd as password from user
思路二:结果集映射resultMap,有点类似起别名。在Mapper配置文件中对不一致的属性名与字段名进行映射即可,不需要全部映射(相同的不需要)。
1 |
<!-- 结果集映射--> |
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
resultMap
元素是 MyBatis 中最重要最强大的元素。- resultMap不仅仅可以解决属性/字段名不一致的问题,还可以实现多表查询等功能。
- 更多使用方法,可以参考官网的文档。
日志
如果数据库操作出现了异常,我们需要排错。日志就是最好的助手。
以前:sout、debug
现在:日志工厂
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
在MyBatis中具体使用哪个日志实现,在设置中设定。
STDOUT_LOGGING
1 |
<!--设置中,开启日志--> |
在MyBatis核心配置文件中开启即可使用:
1 |
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. |
LOG4J
Log4j是什么?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
导入依赖(pom.xml文件)
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2021-12-20日,Apache Log4j2连续曝光了三个漏洞。官方已发布 2.17.0 版本修复漏洞,注意不要使用比2.17.0低的版本。(受影响版本为2.0~2.14,此处使用的是1.2不受影响)
log4j.properties(放在resources文件夹下),各个参数的含义可参考这篇博客。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26#将等级为DEBUG(大于等于)的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
#日志级别有 ALL < TRACE < DEBUG <INFO <WARN <ERROR <FATAL <OFF
DEBUG,console,file =
#控制台输出的相关设置
org.apache.log4j.ConsoleAppender =
System.out =
DEBUG =
org.apache.log4j.PatternLayout =
[%c]-%m%n =
#文件输出的相关设置
org.apache.log4j.RollingFileAppender =
#注意这个日志文件输出位置,项目根目录下的”log/log4j.log“
./log/log4j.log =
10mb =
DEBUG =
org.apache.log4j.PatternLayout =
[%p][%d{yy-MM-dd}][%c]%m%n =
#日志输出级别
DEBUG =
DEBUG =
DEBUG =
DEBUG =
DEBUG =配置log4j为日志的实现(在核心配置文件中)
1
2
3
4
5<!--设置中,开启日志-->
<settings>
<!--LOG4J-->
<setting name="logImpl" value="LOG4J"/>
</settings>简单使用
在要使用Log4j的类中,导入包
import org.apache.log4j.Logger;
日志对象,参数为当前类的class
1
2static Logger logger = Logger.getLogger(当前类.class);
//或者在类上使用注解 @Log4j(Lombok提供的注解)日志级别
1
2
3
4
5logger.info("info:普通info信息。可代替sout使用");
logger.debug("debug:debug信息");
logger.error("error:error信息");
//使用注解@Log4j时,使用 log.info("普通info信息") 输出日志(变量log)
分页
为什么要分页?
- 减少数据的处理量
Limit
使用limit分页
1 |
select * from user limit startIndex,pageSize; |
使用MyBatis实现分页,核心SQL:
接口
1
2//分页
List<User> getUserByLimit(Map<String,Integer> map);mapper.xml
1
2
3
4<!-- 分页-->
<select id="getUserByLimit" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize};
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void getUserByLimit(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);//开始页码(下标
map.put("pageSize",4);//页面大小
List<User> userList = mapper.getUserByLimit(map);
System.out.println("分页查询:");
for (User user:userList) {
System.out.println(user);
}
sqlSession.close();
}
//结果(前边开启了Log4j日志,所以输出比较多):
...
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1634132079.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6166e06f]
[com.qsdbl.mapper.UserMapper.getUserByLimit]-==> Preparing: select * from user limit ?,?;
[com.qsdbl.mapper.UserMapper.getUserByLimit]-==> Parameters: 0(Integer), 4(Integer)
[com.qsdbl.mapper.UserMapper.getUserByLimit]-<== Total: 4
分页查询:
User{id=1, name='小黑', passwd='123456', type='common', time='2021-01-21 11:41:36.0', timeup='2021-01-21 11:41:36.0'}
User{id=2, name='小红', passwd='123456', type='common', time='2021-01-21 11:42:34.0', timeup='2021-01-21 11:42:34.0'}
User{id=3, name='小花', passwd='123456', type='common', time='2021-01-21 11:42:35.0', timeup='2021-01-21 11:42:35.0'}
User{id=4, name='小明', passwd='123456', type='common', time='2021-01-21 11:42:35.0', timeup='2021-01-21 11:42:35.0'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6166e06f]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6166e06f]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1634132079 to pool.
Process finished with exit code 0
RowBounds
RowBounds实现分页。MyBatis官方不建议(已废弃)
不使用SQL实现分页
接口
1
2//分页
List<User> getUserByRowBounds();mapper.xml。sql语句为查询所有数据。
1
2
3
4<!-- 分页-->
<select id="getUserByRowBounds" resultType="user">
select * from user;
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void getUserByRowBounds(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//通过Java代码层面实现分页
RowBounds rowBounds = new RowBounds(0, 4);
List<User> userList = sqlSession.selectList("com.qsdbl.mapper.UserMapper.getUserByRowBounds",null,rowBounds);
System.out.println("分页查询:");
for (User user:userList) {
System.out.println(user);
}
sqlSession.close();
}
//结果(前边开启了Log4j日志,所以输出比较多):
...
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1579526446.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e25a92e]
[com.qsdbl.mapper.UserMapper.getUserByRowBounds]-==> Preparing: select * from user;
[com.qsdbl.mapper.UserMapper.getUserByRowBounds]-==> Parameters:
分页查询:
User{id=1, name='小黑', passwd='123456', type='common', time='2021-01-21 11:41:36.0', timeup='2021-01-21 11:41:36.0'}
User{id=2, name='小红', passwd='123456', type='common', time='2021-01-21 11:42:34.0', timeup='2021-01-21 11:42:34.0'}
User{id=3, name='小花', passwd='123456', type='common', time='2021-01-21 11:42:35.0', timeup='2021-01-21 11:42:35.0'}
User{id=4, name='小明', passwd='123456', type='common', time='2021-01-21 11:42:35.0', timeup='2021-01-21 11:42:35.0'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e25a92e]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e25a92e]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1579526446 to pool.
Process finished with exit code 0
分页插件
了解分页插件pagehelper,案例见这篇博客。
使用注解开发
面向接口编程
- 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
- 根本原因:==解耦==,可扩展,提高复用。分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
- 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的,在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了
- 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约束)与实现的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解
- 接口应有两类:
- 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)
- 一个抽象体可能有多个抽象面。抽象体与抽象面是有区别的
三个面向区别
- 面向对象是指我们考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构
使用注解开发
本质:反射机制实现
底层:动态代理
- 增 - @Insert
- 删 - @Delete
- 改 - @Update
- 查 - @Select
- @Param(“字段名”)注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略,但是建议都加上
- 我们在SQL中引用的就是在@Param注解中设定的字段名
- #{}与${}的区别,前者能够很大程度上防止SQL注入。
示例:
注解在接口上实现
1
2
3
4
5
6
7
8
9
10//#{name},中的name对应实体类User中的属性名
"select * from user where name = #{name}") (
List<User> getUserByName3(User user);
//方法存在多个参数,所有的参数前面必须加上@Param注解
"select * from user where name = #{name}") (
List<User> getUserByName3(@Param("name") String name);
//该方法,在接口UserMapper中。com.qsdbl.mapper.UserMapper.getUserByName3需要在核心配置文件中绑定接口
1
2
3
4
5
6
7
8
9
10
11
12<mappers>
<!-- 使用注解开发,需要绑定接口-->
<mapper class="com.qsdbl.mapper.UserMapper"/>
</mappers>
<!--注解与mapper配置文件 可以一块使用。(不知为何接口绑定要放在前边)-->
<mappers>
<!-- 使用注解开发,需要绑定接口-->
<mapper class="com.qsdbl.mapper.UserMapper"/>
<!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mapper resource="com/qsdbl/mapper/UserMapper.xml"/>
</mappers>测试。使用是一样的,不一样的地方在于接口上使用注解设置sql语句,核心配置文件中绑定接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void getUserByName3(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByName3(new User("小黑"));//参数为User实例
//List<User> userList = mapper.getUserByName3("小黑");//参数为字符串name
System.out.println("查询名字为'小黑'的用户信息:");
for (User user:userList) {
System.out.println(user);
}
sqlSession.close();
}
Lombok
以前的Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString;异常处理;I/O流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok应运而生。
依赖:
1 |
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> |
常用的注解:
1 |
在类上使用,生成无参构造器、get、set、toString、hashcode、equals等 |
更多注解可以参考官网,如何使用Lombok可以参考这篇博客。
IDEA中想要实时看到效果,想要安装IDEA插件“Lombok”(首选项-插件-搜索“Lombok”)。
复杂查询
测试环境搭建
- 导入Lombok
- 新建实体类
- 建立(实体类)对应的Mapper接口
- 建立Mapper.xml文件(若放在resources文件夹下,记得建立多级文件夹与包名保持一致)
- 在核心配置文件中绑定注册我们的Mapper接口或者Mapper配置文件(映射器,使用package标签可同时扫描接口和Mapper配置文件)
- 测试一下
多表查询
高级查询,更多介绍查看这篇博客。
多对一
多个学生对应一个老师
比如:一个班级中的多个学生班主任只有一个。对于学生而言,就是多对一的关系。
重要知识点:MyBatis结果集映射中的–association标签
查询嵌套
MyBatis处理查询嵌套。类似mysql中的子查询(先查询某张表,其结果作为条件再去查询其他表)
1 |
子查询例子: |
准备
创建两个数据表:teacher表、student表。student表中的tid字段作为外键关联teacher表的字段id。
1 |
CREATE TABLE `teacher` ( |
需求
查询所有的学生信息,以及对应的老师的信息。
思路
从student表中查询所有的学生信息
根据查询出来的学生的tid,在teacher表中查询对应的老师信息
1
2
3
4
5
6
7
8
9
10SQL语句如下(这个是联表查询,见后边的应用):
select s.id,s.name,t.name from student s,teacher t where s.tid = t.id;
查询结果如下,符合要求:
s.id, s.name, t.name
1, 小明, 秦老师
2, 小红, 沈老师
3, 小张, 秦老师
4, 小李, 沈老师
5, 小王, 秦老师根据上边的SQL语句可以查询到想要的数据,但是不能在MyBatis中直接使用。要想在MyBatis中实现上述需求,需要用到结果集映射。
代码实现
Mapper配置文件解释:
==1、==从student表中查询所有的学生信息
- ==2、==使用结果集映射,去查询老师信息
- sql语句中,使用结果集映射
resultMap="student-teacher"
1
2
3<select id="getStudent" resultMap="student-teacher">
select * from student
</select>==4、==根据查询出来的学生的tid,在teacher表中查询对应的老师信息
1
2
3
4<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid}
<!--结果集映射中的select="getTeacher"调用这里的SQL,数据由column="tid"传递过来,这里接收数据使用#{tid}。由于只有一个数据传递过来,所以#{tid}中的tid可以改成任意的名字但是建议使用tid便于理解-->
</select>==3、==使用结果集映射,将上边两个SQL整合在一起
- 该结果集映射,id设为
student-teacher
,在上边查询学生信息中使用 - 该结果集映射,返回的数据类型为Student实例(注意我们的需求)。
<association select="getTeacher" column="tid" property="teacher" javaType="Teacher"/>
select="getTeacher"
- 使用上边查询老师信息的select语句。(tid作为参数传递过去)column="tid" property="teacher"
- student数据表中的列名tid 与 实体类Student中的属性teacher进行映射。(tid作为参数传递过去)property="teacher" javaType="Teacher"
- 实体类Student中的属性teacher,数据类型为Teacher(全限定名起的别名为Teacher。“如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。“,也就是说这里的javaType="Teacher"
可以省略不写。)
- 实体类Student的属性teacher要进行结果集映射,其为引用数据类型(对象),所以使用association标签。
javaType=""
指定属性的类型。
1
2
3
4<resultMap id="student-teacher" type="Student">
<!-- 复杂的属性,我们需要单独处理。对象:association 集合:collection -->
<association select="getTeacher" column="tid" property="teacher" javaType="Teacher"/>
</resultMap>- 该结果集映射,id设为
代码实现(注意Student实体类的属性teacher):
1 |
//实体类Student: |
结果嵌套
MyBatis处理结果嵌套。类似mysql中的联表查询(查询多张表,结果嵌套。多表联合查询)
1 |
联表查询例子(上边的需求那里展示的SQL就是这个): |
代码实现:
mapper配置文件解释:
直接使用联表查询的SQL语句,查询出想要的数据再使用结果集映射(
resultMap="student_teacher2"
)对数据进行处理1
2
3<select id="getStudent2" resultMap="student_teacher2">
select student.id as sid,student.name as sname,teacher.name as tname from student,teacher where student.tid = teacher.id
</select>结果集映射
student_teacher2
,对数据进行处理- 返回的数据类型是Student类型,跟上边的一样
- 实体类Student的属性teacher要进行结果集映射,其为引用数据类型(对象),所以使用association标签。
javaType=""
指定属性的类型。 - 查询出来的数据,列sid映射到实体类Student的id属性
- 查询出来的数据,列sname映射到实体类Student的name属性
查询出来的数据,列tname映射到实体类Student的属性teacher对象中的属性name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<resultMap id="student_teacher2" type="Student">
<!-- 属性名、字段名不能有"."所以,这里要设置一下别名-->
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<!--拓展:-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
<!--查询出来的数据,列tname映射到实体类Student的属性teacher对象中的属性name。所以上边的association可以写成下边的result-->
<result property="teacher.name" column="tname"/>
代码实现:
1 |
//1、StudentMapper接口,新增一个方法: |
一对多
比如:一个老师有多个学生。对于老师而言,就是一对多的关系。
重要知识点:MyBatis结果集映射中的–collection标签
结果嵌套
类似mysql中的联表查询(查询多张表,结果嵌套。多表联合查询)
需求:获取指定老师下的所有学生及老师的信息
代码实现:
mapper配置文件解释:
直接使用联表查询的SQL语句,查询出想要的数据再使用结果集映射(
resultMap="teacher_student"
)对数据进行处理- 注意看后边实体类Student、Teacher的变化。
1
2
3
4
5
6
7<!--按照结果嵌套查询。下边的结果集映射中,属性名、字段名不能有".",所以这里要设置一下别名-->
<select id="getTeacher" resultMap="teacher_student">
select t.name as tname,t.id as tid,s.name as sname,s.id as sid
from student as s,teacher as t
where s.tid = t.id and t.id = #{id}
</select>
<!--TeacherMapper接口提供参数id,传递给#{id}-->结果集映射
teacher_student
,对数据进行处理- 返回的数据类型是Teacher类型(实体类中增加了一个
List<Student>
集合,保存该老师对应的学生)
- 返回的数据类型是Teacher类型(实体类中增加了一个
实体类Teacher的属性
studentList
要进行结果集映射,其为List集合,所以使用collection标签。javaType=""
指定属性的类型。集合中的泛型信息,使用ofType=""
指定。- 查询出来的数据,列tid映射到实体类Teacher的id属性
查询出来的数据,列tname映射到实体类Teacher的name属性
-
查询出来的数据,列sid、列sname映射到
List<Student>
集合中的实体类Student - 列sid映射到实体类Student的id属性
- 列sname映射到实体类Student的name属性
1
2
3
4
5
6
7
8
9
10
11<resultMap id="teacher_student" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 复杂的属性,我们需要单独处理。对象:association 集合:collection
javaType="" 指定属性的类型
集合中的泛型信息,我们使用ofType获取-->
<collection property="studentList" javaType="ArrayList" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>-
查询出来的数据,列sid、列sname映射到
代码实现:
1 |
//实体类Student: |
查询嵌套
类似mysql中的子查询(先查询某张表,其结果作为条件再去查询其他表)
Mapper配置文件解释:
==1、==从teacher表中查询所有的老师信息
- ==2、==使用结果集映射,去查询学生信息
- sql语句中,使用结果集映射
resultMap="teacher_student2"
1
2
3<select id="getTeacher2" resultMap="teacher_student2">
select * from teacher where id = #{id}
</select>==4、==根据查询出来的老师的id,在student表中查询对应的学生信息
1
2
3
4
5<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
<!--结果集映射中的select="getStudentByTeacherId"调用这里的SQL,数据由column="id"传递过来,这里接收数据使用#{id}。由于只有一个数据传递过来,所以#{id}中的id可以改成任意的名字但是建议使用id便于理解-->==3、==使用结果集映射,将上边两个SQL整合在一起
- 该结果集映射,id设为
teacher_student2
,在上边第一个select中使用 - 该结果集映射,返回的数据类型为Teacher实例(注意我们的需求)。
<collection select="getStudentByTeacherId" column="id" property="studentList" javaType="ArrayList" ofType="Student"/>
select="getStudentByTeacherId"
- 使用上边查询学生信息的select语句。(id作为参数传递过去)column="id" property="studentList"
- teacher数据表中的列名id 与 实体类Teacher中的属性studentList进行映射。(id作为参数传递过去)property="studentList" javaType="ArrayList"
- 实体类Teacher中的属性studentList,数据类型为ArrayList(List为集合,应该填写他的实现类,这里使用了ArrayList。不过实测List也可以)property="studentList" javaType="ArrayList" ofType="Student"
- 实体类Teacher的属性studentList
要进行结果集映射,其为List集合,所以使用collection标签。javaType=""
指定属性的类型。集合中的泛型信息,使用ofType=""
指定。
1
2
3
4
5<resultMap id="teacher_student2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection select="getStudentByTeacherId" column="id" property="studentList" javaType="ArrayList" ofType="Student"/>
</resultMap>- 该结果集映射,id设为
代码实现:
1 |
//1、TeacherMapper接口,新增一个方法,有参数id: |
小结
- 关联 - association 【多对一】
- 集合 - collection 【一对多】
- JavaType & ofType
- javaType,用来指定实体类中属性的类型
- ofType,用来指定映射到集合(例如List)中的实体类类型(泛型中的约束类型)
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段名的问题
- 如果问题不好排查错误,可以使用日志,建议使用Log4j
面试高频:
- MyBatis引擎
- 例如:innoDB底层原理
- 索引
- 索引优化
动态SQL
什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句
利用动态SQL这一特性可以彻底摆脱这种痛苦。
1 |
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。 |
环境搭建
使用到的数据表(COMMENT,设置备注)
1
2
3
4
5
6
7CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8导包(Maven依赖)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<!-- 导入依赖-->
<dependencies>
<!-- mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 日志,log4j-->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- Lombok-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
</dependencies>编写配置文件(在resources文件夹下)
- 新建MyBatis核心配置文件mybatis-config.xml。
- properties - 引入外部配置文件,起解耦的作用。
- typeAliases - 起别名,mapper配置文件中就不需要写长长的全限定名。
- mappers - 包扫描自动注册映射器,就不需要手动给每个mapper配置文件注册。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--核心配置文件-->
<configuration>
<!-- 引入外部配置文件-->
<properties resource="db.properties"/>
<!-- 可以给实体类 起别名。包扫描-->
<typeAliases>
<package name="com.qsdbl.bean"/>
</typeAliases>
<environments default="mydevelopment-mysql">
<environment id="mydevelopment-mysql">
<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>
<package name="com.qsdbl.mapper"/>
</mappers>
</configuration>再写一个配置文件(在上边第九行引入)db.properties
1
2
3
4
5#mysql 8.x以上,使用的驱动是“com.mysql.cj.jdbc.Driver”
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://qsdbl.site:3306/mypos?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=jack
password=12345Qsdbl---
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package com.qsdbl.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//SqlSessionFactory -> SqlSession
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用MyBatis第一步:获取SqlSessionFactory对象
String resource = "mybatis-config.xml";//加载resources文件夹下的文件,直接使用文件名即可
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);//添加参数true,执行SQL后会自动提交事务
}
}用于获取SqlSession实例。一般放在utils包下。
编写实体类(可以放在bean、pojo、entity包下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.qsdbl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//在类上使用,生成无参构造器、get、set、toString、hashcode、equals等
//在类上使用,生成有参构造器(全部)
//在类上使用,生成无参构造器
public class Blog {
private int id;
private String title;
private String author;
private java.util.Date createTime;//使用util包下的Date(不要使用sql包下的)
private int views;
}使用Lombok注解,不需要手写getter、setter、构造器等。
编写实体类对应的mapper接口和mapper.xml配置文件
写好mapper接口后,在resources文件夹下,创建与包相应的目录结构再创建mapper.xml配置文件。注意还要到核心配置文件中进行注册,不过前边使用
<package name="com.qsdbl.mapper"/>
包扫描自动注册了不需要手动注册,想要注意两点:1、配置文件与接口要同名。2、resources文件夹下创建的目录要与包对应上1
2
3
4
5
6
7
8
<!--mapper配置文件(接口BlogMapper)-->
<mapper namespace="com.qsdbl.mapper.BlogMapper">
</mapper>
Where-If
动态SQL:where、if。
mapper配置文件解释:
select * from blog where 1=1
,可以查询出全部的数据。1=1
不会影响正常查询,作用是为了方便下边的and条件拼接使用if标签,添加动态SQL。在
test=“”
中写判断条件,title为参数map(parameterType="map"
)中提供的数据。1
2
3<if test="title != null">
and title = #{title}
</if>一定要注意SQL语句拼接是否正确
改进:使用where标签代替
where 1=1
where标签,官方文档解释:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
1
2
3
4
5
6
7
8
9
10
11<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
代码演示:
1 |
//1、BlogMapper接口,新增一个方法,有参数map: |
choose
choose (when, otherwise) - 类似java中的switch-case。
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
- when,相当于java中的case
- otherwise,相当于java中的default
1 |
<!--choose-when,只选择一条,所以第二个when标签里不加and(即使title、author都不为null也是选择一条,title先被匹配到所以会被选择到--> |
trim
trim (where, set) - where、set的本质是trim标签。
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。==where标签替换掉原本SQL语句中的where命令。==
- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。==set标签主要用于update语句,替换掉原本SQL语句中的set命令。==
1 |
<update id="updateBlog" parameterType="map"> |
SQL片段
将一些可以复用的语句进行封装,提高复用性。
使用SQL标签抽取公共部分
1
2
3
4
5
6
7
8
9
10
11
12<!--将上边的例子中的if部分提取出来封装在SQL标签中。id名取为”if-title-author-views“-->
<sql id="if-title-author-views">
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views != null">
views = #{views}
</if>
</sql>在需要的地方使用include标签引用即可
1
2
3
4
5
6
7
8<update id="updateBlog" parameterType="map">
update blog
<set>
<!--使用include标签引用封装好的SQL片段,refid为要引用的SQL片段的id-->
<include refid="if-title-author-views"></include>
</set>
where id = #{id}
</update>
注意事项:
- 最好基于表单来定义SQL片段
- SQL片段内最好不要存在where标签(越简单越好)
foreach
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
1 |
select * from blog where (id = 1 or id = 2 or id = 3); |
mapper配置文件解释:
使用foreach标签,对参数集合进行遍历,动态增加SQL语句。
collection=""
中写携带参数的集合,可以是任何可迭代对象(本例子中,由参数map提供)item=""
中写从集合中取出的数据的变量引用(集合项)index=""
中写从集合中取出的数据的索引open=""
中写foreach标签动态拼接SQL语句后,在开头添加的字符(SQL语句)close=""
中写foreach标签动态拼接SQL语句后,在结尾添加的字符(SQL语句)-
separator=""
中写foreach标签动态拼接SQL语句时使用的分割符
1
2
3<foreach collection="ids" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>注意:where标签,只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。(所以在上边例子中当ids集合为空时,不会拼接出诸如
id = null
的SQL语句)一定要注意SQL语句拼接是否正确
代码演示:
1 |
//1、BlogMapper接口,新增一个方法,有参数map: |
小结
- if追加多个条件,choose只会追加一个,匹配一个when后自动“break”
- where-if,可以选择多个SQL语句拼接在后边
- choose-when,类似Java中的switch-case,只选择一条
- ==所谓动态SQL,本质还是SQL语句,只是我们可以在SQL层面去执行一个逻辑代码==
- 动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
- 建议:先在mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可
缓存
- 什么是缓存(cache)?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
- 为什么要使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据(可以使用缓存)
一级缓存
- 一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试一级缓存
测试:
开启Log4j日志
测试在一个SqlSession中查询两次相同记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14public void queryUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();//开启SqlSession
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);//第一次查询数据
System.out.println("查询id为1的用户信息:\n"+user1);
System.out.println("============分隔符============");
User user2 = mapper.queryUserById(1);//第二次查询数据
System.out.println("查询id为1的用户信息:\n"+user2);
System.out.println("user1 == user2:"+(user1 == user2));
sqlSession.close();//关闭SqlSession
}查看日志输出
缓存失效的情况
缓存失效的情况:
查询不同的东西(例如:两次查询,查询两个用户信息)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void queryUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println("查询id为1的用户信息:\n"+user1);
System.out.println("============分隔符============");
User user2 = mapper.queryUserById(2);
System.out.println("查询id为2的用户信息:\n"+user2);
System.out.println("user1 == user2:"+(user1 == user2));
sqlSession.close();
}“增、删、改”操作,可能会改变原来的数据,所以必定会刷新缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void updateUser(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println("查询id为"+user1.getId()+"的用户信息:\n"+user1);
user1.setName("马克思");
mapper.updateUser(user1);
System.out.println("将其名字改为:"+user1.getName());
System.out.println("============分隔符============");
User user2 = mapper.queryUserById(1);
System.out.println("查询id为"+user2.getId()+"的用户信息:\n"+user2);
System.out.println("user1 == user2:"+(user1 == user2));
if (user1 == user2){
System.out.println("由于mybatis一级缓存的原因,两个user对象相同(地址)");
}else{
System.out.println("由于\"增、删、改\"操作会刷新缓存,所以两个user对象不相同(地址)");
}
sqlSession.close();
}手动清理缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void queryUserById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println("查询id为1的用户信息:\n"+user1);
System.out.println("============分隔符============");
sqlSession.clearCache();//手动清理缓存
User user2 = mapper.queryUserById(1);
System.out.println("查询id为1的用户信息:\n"+user2);
System.out.println("user1 == user2:"+(user1 == user2));
sqlSession.close();
}
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间对应一个二级缓存
- 工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(mapper)中
步骤
核心配置文件中开启全局(二级)缓存(默认就是开启的,所以这一步可以省略。也可起到提示的作用)
1
2<!--显式的开启全局缓存(默认就是开启的)-->
<setting name="cacheEnabled" value="true"/>在要使用二级缓存的mapper配置文件中开启
1
2
3
4
5
6
7
8
9<!--在mapper配置文件中启用二级缓存(只需要一个cache标签即可,不需要其他配置)-->
<cache/>
<!--或添加一些自定义配置:-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>测试
mapper配置文件中,设置
readOnly="true"
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
- 可读写的缓存,readOnly=”false”,序列化与反序列化后的对象(实体类需要实现Serializable接口)与原对象不是同一个对象,故地址不同。
- 只读的缓存,readOnly=”true”,测试中需要返回缓存对象的相同实例,所以要设置为true
1
2
3<!--为了测试需要,设置readOnly="true"(默认为false)-->
<cache
readOnly="true"/>测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void testCache(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//UserMapper
User user1 = mapper.queryUserById(1);
System.out.println("查询id为1的用户信息:\n"+user1);
sqlSession.close();
System.out.println("= = = = 关闭SqlSession -- 一级缓存失效 = = = = ");
SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);//UserMapper
User user2 = mapper2.queryUserById(1);
System.out.println("查询id为1的用户信息:\n"+user2);
sqlSession2.close();
System.out.println("= = = = 关闭SqlSession -- 一级缓存失效 = = = = ");
System.out.println("user1 == user2:"+(user1 == user2));
}测试结果:
未开启二级缓存时:
开启二级缓存后:
小结
- 只要开启了二级缓存,在同一个mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交或者关闭的时候,才会提交到二级缓存中
缓存原理
自定义缓存-Ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
导入依赖
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>ehcache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>在mapper配置文件中指定使用我们的ehcache缓存实现
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
自定义缓存了解即可,一般使用Redis数据库来做缓存。
搭建环境-总结
快速搭建MyBatis开发环境,参考这里。
整合
整合Spring点这里查看。
整合SpringBoot点这里查看(PageHelper分页插件)。