共计 31797 个字符,预计需要花费 80 分钟才能阅读完成。
<img src=”http://student.kaikeba.com//assets/blue_logo-57d711624a.png” style=”float:right;width:120px;padding-top:26px;” />
课堂主题
Mybatis 基于 XML 和注解方式的开发应用专题
课堂目标
- 主键返回(mybatis 的自增主键或者非自增主键)
- 批量查询
- 动态传参
- 查询缓存(一级缓存、二级缓存)
- 延迟加载(侵入式延迟加载、深度延迟加载)
- 关联查询(一对一、一对映射)
- 逆向工程
- PageHelper 分页插件
- 注解开发
知识要点
[TOC]
介绍篇
认识自己
开发人员 OR 研发人员?
主要是使用成熟的框架去开发应用功能,还是使用 JavaEE、JVM、并发编程、NIO/Netty 等知识点实现编写自定义框架或者解决高并发场景下的非功能性需求,比如如何提高并发能力等?
如何进行接下来的学习呢?
他山之石,可以攻玉!!学习人家的框架,写出自己的框架。
认识框架
什么是框架
摘自百度:
可以说,一个框架是一个 可复用 的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文 (Context) 关系。因此构件库的大规模重用也需要框架。
为什么使用框架
- 因为 软件系统 发展到今天 已经很复杂了 ,特别是 服务器端软件 ,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的 框架 , 就相当于让别人帮你完成一些基础工作 ,你 只需要集中精力完成系统的业务逻辑设计。
- 而且 框架 一般是成熟,稳健的,它可以处理系统很多细节问题,比如,事务处理,安全性,数据流控制等问题。
- 还有 框架 一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它 是不断升级的,你 可以直接享受别人升级代码带来的好处。
软件开发的三层结构
我们用三层结构主要是使项目结构更清楚,分工更明确,有利于后期的维护和升级.
三层结构包含:表现层,业务层,持久层
认识设计模式
设计模式概述
- 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
- 设计模式是软件开发人员在软件开发过程中面临的一般 问题的解决方案 。这些解决方案是众多软件开发人员经过相当长的一段时间的 试验和错误总结出来 的。
- 设计模式是一套被 反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
- 设计模式不是一种方法和技术,而 是一种思想。
- 设计模式和具体的语言无关 ,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚, 使设计的程序可复用。
- 学习设计模式能够促进对面向对象思想的理解,反之亦然。它们相辅相成。
设计模式的类型
总体来说,设计模式分为 三类 23 种:
-
创建型(5 种):
工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式
-
结构型(7 种):
适配器模式、装饰模式、代理模式
、外观模式、桥接模式、组合模式、享元模式 -
行为型(11 种):
模板方法模式、策略模式
、观察者模式、中介者模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式、备忘录模式
认识 MyBatis
mybatis 参考网址:http://www.mybatis.org/mybati…
Github 源码地址:https://github.com/mybatis/my…
Mybatis 是什么
MyBatis 是一款优秀的 持久层框架 ,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集,它可以使用简单的XML 或注解 来配置和映射 SQL 信息,将接口和 Java 的 POJOs(Plain Old Java Objects, 普通的 Java 对象)映射成数据库中的记录。
Mybatis 的由来
- MyBatis 本是 apache 的一个开源项目 iBatis。
- 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis。
- 2013 年 11 月迁移到 Github。
ORM 是什么
对象 - 关系映射(OBJECT/RELATIONALMAPPING,简称 ORM),是随着面向对象的软件开发方法发展而产生的。用来把对象模型表示的对象映射到基于 SQL 的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的 SQL 语句打交道,只需简单的操作实体对象的属性和方法。ORM 技术是在对象和关系之间提供了一条桥梁,前台的对象型数据和数据库中的关系型的数据通过这个桥梁来相互转化。
ORM 框架和 MyBatis 的区别
对比项 | Mybatis | Hibernate |
---|---|---|
市场占有率 | 高 | 高 |
适合的行业 | 互联网 电商 项目 | 传统的(ERP CRM OA) |
性能 | 高 | 低 |
Sql 灵活性 | 高 | 低 |
学习门槛 | 低 | 高 |
Sql 配置文件 | 全局配置文件、映射文件 | 全局配置文件、映射文件 |
ORM | 半自动化 | 完全的自动化 |
数据库无关性 | 低 | 高 |
入门篇
编码流程
- 编写全局配置文件:SqlMapConfig.xml
- 映射文件:xxxMapper.xml
- 编写 dao 代码:xxxDao 接口、xxxDaoImpl 实现类
- POJO 类
- 单元测试类
需求
1、根据用户 id 查询一个用户信息
2、根据用户名称模糊查询用户信息列表
3、添加用户
项目搭建
- 创建 maven 工程:mybatis-demo
- POM 文件
<dependencies>
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 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>
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
- UserMapper.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="test">
</mapper>
- PO 类
public class User {
private int id;
private String username;
private Date birthday;
private String sex;
private String address;
// getter\setter 方法
}
需求实现
查询用户
映射文件
<!-- 根据 id 获取用户信息 -->
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User">
select * from user where id = #{id}
</select>
<!-- 根据名称模糊查询用户列表 -->
<select id="findUserByUsername" parameterType="java.lang.String"
resultType="com.kkb.mybatis.po.User">
select * from user where username like '%${value}%'
</select>
配置说明:
- parameterType:定义输入参数的 Java 类型,- resultType:定义结果映射类型。- #{}:相当于 JDBC 中的?占位符
- #{id}表示使用 preparedstatement 设置占位符号并将输入变量 id 传到 sql。- ${value}:取出参数名为 value 的值。将 ${value}占位符替换。注意:如果是取简单数量类型的参数,括号中的参数名称必须为 value
dao 接口和实现类
public interface UserDao {public User findUserById(int id) throws Exception;
public List<User> findUsersByName(String name) throws Exception;
}
- 生命周期(作用范围)
- sqlsession:方法级别
- sqlsessionFactory:全局范围(应用级别)
- sqlsessionFactoryBuilder:方法级别
public class UserDaoImpl implements UserDao {
// 注入 SqlSessionFactory
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){this. sqlSessionFactory = sqlSessionFactory;}
private SqlSessionFactory sqlSessionFactory;
@Override
public User findUserById(int id) throws Exception {SqlSession session = sqlSessionFactory.openSession();
User user = null;
try {
// 通过 sqlsession 调用 selectOne 方法获取一条结果集
// 参数 1:指定定义的 statement 的 id, 参数 2:指定向 statement 中传递的参数
user = session.selectOne("test.findUserById", id);
System.out.println(user);
} finally{session.close();
}
return user;
}
@Override
public List<User> findUsersByName(String name) throws Exception {SqlSession session = sqlSessionFactory.openSession();
List<User> users = null;
try {users = session.selectList("test.findUsersByName", name);
System.out.println(users);
} finally{session.close();
}
return users;
}
}
测试代码
public class MybatisTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws Exception {SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = sessionFactoryBuilder.build(inputStream);
}
@Test
public void testFindUserById() {UserDao userDao = new UserDaoImpl(sqlSessionFactory);
User user = userDao.findUserById(22);
System.out.println(user);
}
@Test
public void testFindUsersByName() {UserDao userDao = new UserDaoImpl(sqlSessionFactory);
List<User> users = userDao.findUsersByName("老郭");
System.out.println(users);
}
}
{}和 ${}区别
- 区别 1
#{}:相当于 JDBC SQL 语句中的占位符? (PreparedStatement)
${} : 相当于 JDBC SQL 语句中的连接符合 + (Statement)
- 区别 2
#{}:进行输入映射的时候,会对参数进行类型解析(如果是 String 类型,那么 SQL 语句会自动加上’’)${} : 进行输入映射的时候,将参数原样输出到 SQL 语句中
- 区别 3
#{}:如果进行简单类型(String、Date、8 种基本类型的包装类)的输入映射时,#{}中参数名称可以任意
${} : 如果进行简单类型(String、Date、8 种基本类型的包装类)的输入映射时,${}中参数名称必须是 value
- 区别 4
${} : 存在 SQL 注入问题,使用 OR 1=1 关键字将查询条件忽略
添加用户
#{}:是通过反射获取数据的 —StaticSqlSource
${}:是通过 OGNL 表达式会随着对象的嵌套而相应的发生层级变化 –DynamicSqlSource
映射文件
<!-- 添加用户 -->
<insert id="insertUser" parameterType="com.kkb.mybatis.po.User">
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
dao 接口和实现类
public interface UserDao {public void insertUser(User user) throws Exception;
}
public class UserDaoImpl implements UserDao {
// 注入 SqlSessionFactory
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){this. sqlSessionFactory = sqlSessionFactory;}
private SqlSessionFactory sqlSessionFactory;
@Override
Public void insertUser(User user) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();
try {sqlSession.insert("test.insertUser", user);
sqlSession.commit();} finally{session.close();
}
}
}
测试代码
@Override
Public void insertUser(User user) throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();
try {sqlSession.insert("insertUser", user);
sqlSession.commit();} finally{session.close();
}
}
主键返回
<insert id="insertUser" parameterType="com.kkb.mybatis.po.User">
<!-- selectKey 将主键返回,需要再返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address});
</insert>
添加 selectKey 标签实现主键返回。
- keyProperty: 指定返回的主键,存储在 pojo 中的哪个属性
- order:selectKey 标签中的 sql 的执行顺序,是相对与 insert 语句来说。由于 mysql 的自增原理,执行完 insert 语句之后才将主键生成,所以这里 selectKey 的执行顺序为 after。
- resultType: 返回的主键对应的 JAVA 类型
- LAST_INSERT_ID(): 是 mysql 的函数,返回 auto_increment 自增列新记录 id 值。
OGNL
对象导航图语言
|—User(参数值对象)
|–username– 张三
|–birthday
|–sex– 男
|–dept — Department
|–name
|–no
OGNL 表达式去获取 Department 对象的 name 属性:dept.name
基础应用篇
mapper 代理开发方式
此处使用的是 JDK 的动态代理方式,延迟加载使用的 cglib 动态代理方式
代理理解
代理分为静态代理和动态代理。此处先不说静态代理,因为 Mybatis 中使用的代理方式是动态代理。
动态代理分为两种方式:
- 基于 JDK 的动态代理 – 针对有 接口的类 进行动态代理
- 基于 CGLIB 的动态代理 – 通过 子类 继承 父类 的方式去进行代理。
XML 方式
- 开发方式
只需要开发 Mapper 接口(dao 接口)和 Mapper 映射文件,不需要编写实现类。
- 开发规范
Mapper 接口开发方式需要遵循以下规范:
1、Mapper 接口的类路径与 Mapper.xml 文件中的 namespace 相同。
2、Mapper 接口方法名称和 Mapper.xml 中定义的每个 statement 的 id 相同。
3、Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同。
4、Mapper 接口方法的返回值类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同。
- mapper 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mybatis.mapper.UserMapper">
<!-- 根据 id 获取用户信息 -->
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User">
select * from user where id = #{id}
</select>
</mapper>
- mapper 接口
/**
* 用户管理 mapper
*/
public interface UserMapper {
// 根据用户 id 查询用户信息
public User findUserById(int id) throws Exception;
}
- 全局配置文件中加载映射文件
<!-- 加载映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
- 测试代码
public class UserMapperTest{
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception {
//mybatis 配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用 SqlSessionFactoryBuilder 创建 sessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
// 获取 session
SqlSession session = sqlSessionFactory.openSession();
// 获取 mapper 接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 调用代理对象方法
User user = userMapper.findUserById(1);
System.out.println(user);
// 关闭 session
session.close();}
}
注解方式
- 开发方式
只需要编写 mapper 接口文件接口。
- mapper 接口
public interface AnnotationUserMapper {
// 查询
@Select("SELECT * FROM user WHERE id = #{id}")
public User findUserById(int id);
// 模糊查询用户列表
@Select("SELECT * FROM user WHERE username LIKE'%${value}%'")
public List<User> findUserList(String username);
// 添加并实现主键返回
@Insert("INSERT INTO user (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})")
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", resultType = int.class, before = false)
public void insertUser(User user);
}
- 测试代码
public class AnnotationUserMapperTest {
private SqlSessionFactory sqlSessionFactory;
/**
* @Before 注解的方法会在 @Test 注解的方法之前执行
*
* @throws Exception
*/
@Before
public void init() throws Exception {
// 指定全局配置文件路径
String resource = "SqlMapConfig.xml";
// 加载资源文件(全局配置文件和映射文件)InputStream inputStream = Resources.getResourceAsStream(resource);
// 还有构建者模式,去创建 SqlSessionFactory 对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() {SqlSession sqlSession = sqlSessionFactory.openSession();
AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class);
User user = userMapper.findUserById(1);
System.out.println(user);
}
@Test
public void testFindUserList() {SqlSession sqlSession = sqlSessionFactory.openSession();
AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class);
List<User> list = userMapper.findUserList("老郭");
System.out.println(list);
}
@Test
public void testInsertUser() {SqlSession sqlSession = sqlSessionFactory.openSession();
AnnotationUserMapper userMapper = sqlSession.getMapper(AnnotationUserMapper.class);
User user = new User();
user.setUsername("开课吧 -2");
user.setSex("1");
user.setAddress("致真大厦");
userMapper.insertUser(user);
System.out.println(user.getId());
}
}
全局配置文件
配置内容
SqlMapConfig.xml 中配置的内容和顺序如下:
properties(属性)settings(全局配置参数)typeAliases(类型别名)typeHandlers(类型处理器)--Java 类型 --JDBC 类型 ---> 数据库类型转换
objectFactory(对象工厂)plugins(插件)-- 可以在 Mybatis 执行 SQL 语句的流程中,横叉一脚去实现一些功能增强,比如 PageHelper 分页插件,就是第三方实现的一个插件
environments(环境集合属性对象)environment(环境子属性对象)transactionManager(事务管理)dataSource(数据源)mappers(映射器)
properties 标签
SqlMapConfig.xml 可以引用 java 属性文件中的配置信息。
1、在 classpath 下定义 db.properties 文件,
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
2、在 SqlMapConfig.xml 文件中,引用 db.properties 中的属性,具体如下:
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
properties 标签除了可以使用 resource 属性,引用 properties 文件中的属性。还可以在 properties 标签内定义 property 子标签来定义属性和属性值,具体如下:
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
</properties>
注意:MyBatis 将按照下面的顺序来加载属性:
- 读取 properties 元素体内定义的属性。
- 读取 properties 元素中 resource 或 url 加载的属性,它会覆盖已读取的同名属性。
typeAlias 标签
别名的作用:就是为了简化映射文件中 parameterType 和 ResultType 中的 POJO 类型名称编写。
默认支持别名
别名 | 映射的类型 |
---|---|
_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 |
map | Map |
自定义别名
在 SqlMapConfig.xml 中进行如下配置:
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.kkb.mybatis.po.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以)-->
<package name="com.kkb.mybatis.po"/>
</typeAliases>
mappers 标签
<mapper resource=””/>
使用相对于类路径的资源
如:
<mapper resource="sqlmap/User.xml" />
<mapper url=””>
使用绝对路径加载资源
如:
<mapper url="file://d:/sqlmap/User.xml" />
<mapper class=””/>
使用 mapper 接口类路径,加载映射文件。
如:
<mapper class="com.kkb.mybatis.mapper.UserMapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
<package name=””/>
注册指定包下的所有 mapper 接口,来加载映射文件。
如:
<package name="com.kkb.mybatis.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
输入映射和输出映射
parameterType(输入类型)
parameterType 属性可以映射的输入参数 Java 类型有:简单类型、POJO 类型、Map 类型、List 类型(数组)。
- Map 类型和 POJO 类型的用法类似,本课程只讲 POJO 类型的相关配置。
- List 类型在动态 SQL 部分进行讲解。
传递简单类型
参考入门案例中用户查询的案例。
传递 pojo 对象
参考入门案例中的添加用户的案例。
传递 pojo 包装对象
包装对象:pojo 类中嵌套 pojo。
需求
通过包装 POJO 传递参数,完成用户查询。
QueryVO
定义包装对象 QueryVO
public class QueryVO {private User user;}
SQL 语句
SELECT * FROM user where username like '% 小明 %'
Mapper 文件
<!-- 使用包装类型查询用户
使用 ognl 从对象中取属性值,如果是包装对象可以使用. 操作符来取内容部的属性
-->
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user where username like '%${user.username}%'
</select>
Mapper 接口
/**
* 用户管理 mapper
*/
public interface UserMapper {
// 综合查询用户列表
public List<User> findUserList(QueryVo queryVo)throws Exception;
}
测试方法
在 UserMapperTest 测试类中,添加以下测试代码:
@Test
public void testFindUserList() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得 mapper 的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建 QueryVo 对象
QueryVo queryVo = new QueryVo();
// 创建 user 对象
User user = new User();
user.setUsername("小明");
queryVo.setUser(user);
// 根据 queryvo 查询用户
List<User> list = userMapper.findUserList(queryVo);
System.out.println(list);
sqlSession.close();}
resultType(输出类型)
resultType 属性可以映射的 java 类型有:简单类型、POJO 类型、Map 类型。
不过 Map 类型和 POJO 类型的使用情况类型,所以只需讲解 POJO 类型即可。
使用要求
使用 resultType 进行输出映射时,要求 sql 语句中 查询的列名 和要映射的 pojo 的属性名 一致。
映射简单类型
案例需求
查询用户记录总数。
Mapper 映射文件
<!-- 获取用户列表总数 -->
<select id="findUserCount" resultType="int">
select count(1) from user
</select>
Mapper 接口
// 查询用户总数
public int findUserCount() throws Exception;
测试代码
在 UserMapperTest 测试类中,添加以下测试代码:
@Test
public void testFindUserCount() throws Exception {SqlSession sqlSession = sessionFactory.openSession();
// 获得 mapper 的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int count = userMapper.findUserCount(queryVo);
System.out.println(count);
sqlSession.close();}
注意:输出简单类型必须查询出来的结果集只有一列。
映射 pojo 对象
注意:不管是单个 POJO 还是 POJO 集合,在使用 resultType 完成映射时,用法一样。
参考入门程序之根据用户 ID 查询用户信息和根据名称模糊查询用户列表的案例
resultMap
使用要求
如果 sql 查询列名和 pojo 的属性名可以不一致,通过 resultMap 将列名和属性名作一个对应关系,最终将查询结果映射到指定的 pojo 对象中。
注意:resultType 底层也是通过 resultMap 完成映射的。
需求
将以下 sql 的查询结果进行映射:
SELECT id id_,username username_,birthday birthday_ FROM user
Mapper 接口
// resultMap 入门
public List<User> findUserListResultMap() throws Exception;
Mapper 映射文件
由于 sql 查询列名和 User 类属性名不一致,所以不能使用 resultType 进行结构映射。
需要定义一个 resultMap 将 sql 查询列名和 User 类的属性名对应起来,完成结果映射。
<!-- 定义 resultMap:将查询的列名和映射的 pojo 的属性名做一个对应关系 -->
<!--
type:指定查询结果要映射的 pojo 的类型
id:指定 resultMap 的唯一标示
-->
<resultMap type="user" id="userListResultMap">
<!--
id 标签:映射查询结果的唯一列(主键列)column:查询 sql 的列名
property:映射结果的属性名
-->
<id column="id_" property="id"/>
<!-- result 标签:映射查询结果的普通列 -->
<result column="username_" property="username"/>
<result column="birthday_" property="birthday"/>
</resultMap>
<!-- resultMap 入门 -->
<select id="findUserListResultMap" resultMap="userListResultMap">
SELECT id id_,username username_,birthday birthday_ FROM user
</select>
-
<id/>:表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个 <id />
- Property:表示 User 类的属性。
- Column:表示 sql 查询出来的字段名。
- Column 和 property 放在一块儿表示将 sql 查询出来的字段映射到指定的 pojo 类属性上。
- <result/>:普通结果,即 pojo 的属性。
高级应用篇
关联查询
商品订单数据模型
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发,关联查询用户信息为一对一查询。如果从用户信息出发,查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
一对一查询
需求
查询所有订单信息,关联查询下单用户信息。
SQL 语句
SELECT
orders.*,
user.username,
user.address
FROM
orders LEFT JOIN user
ON orders.user_id = user.id
主信息:订单信息
从信息:用户信息
方法一:resultType
学生们自己实现。
方法二:resultMap
使用 resultMap 进行结果映射,定义专门的 resultMap 用于映射一对一查询结果。
创建扩展 po 类
创建 OrdersExt 类(该类用于结果集封装),加入 User 属性,user 属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个 User 对象存储关联查询的用户信息。
public class OrdersExt extends Orders {
private User user;// 用户对象
// get/set。。。。}
Mapper 映射文件
在 UserMapper.xml 中,添加以下代码:
<!-- 查询订单关联用户信息使用 resultmap -->
<resultMap type="OrdersExt" id="ordersAndUserRstMap">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 一对一关联映射 -->
<!--
property:Orders 对象的 user 属性
javaType:user 属性对应 的类型
-->
<association property="user" javaType="com.kkb.mybatis.po.User">
<!-- column:user 表的主键对应的列 property:user 对象中 id 属性 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="findOrdersAndUserRstMap" resultMap="ordersAndUserRstMap">
SELECT
o.id,
o.user_id,
o.number,
o.createtime,
o.note,
u.username,
u.address
FROM
orders o
JOIN `user` u ON u.id = o.user_id
</select>
association:表示进行一对一关联查询映射
property:表示关联查询的结果存储在 com.kkb.mybatis.po.Orders 的 user 属性中
javaType:表示关联查询的映射结果类型
Mapper 接口
在 UserMapper 接口中,添加以下接口方法:
public List<OrdersExt> findOrdersAndUserRstMap() throws Exception;
测试代码
在 UserMapperTest 测试类中,添加测试代码:
public void testfindOrdersAndUserRstMap()throws Exception{
// 获取 session
SqlSession session = sqlSessionFactory.openSession();
// 获限 mapper 接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
// 查询订单信息
List<OrdersExt> list = userMapper.findOrdersAndUserRstMap();
System.out.println(list);
// 关闭 session
session.close();}
小结
使用 resultMap 进行结果映射时,具体是使用 association 完成关联查询的映射,将关联查询信息映射到 pojo 对象中。
一对多查询
需求
查询所有用户信息及用户关联的订单信息。
SQL 语句
SELECT
u.*,
o.id oid,
o.number,
o.createtime,
o.note
FROM
`user` u
LEFT JOIN orders o ON u.id = o.user_id
主信息:用户信息
从信息:订单信息
分析
在一对多关联查询时,只能使用 resultMap 进行结果映射。
1、一对多关联查询时,sql 查询结果有多条,而映射对象是一个。
2、resultType 完成结果映射的方式的一条记录映射一个对象。
3、resultMap 完成结果映射的方式是以 [主信息] 为主对象,[从信息]映射为集合或者对象,然后封装到主对象中。
修改 po 类
在 User 类中加入 List<Orders> orders 属性
Mapper 映射文件
在 UserMapper.xml 文件中,添加以下代码:
<resultMap type="user" id="userAndOrderRstMap">
<!-- 用户信息映射 -->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!-- 一对多关联映射 -->
<collection property="orders" ofType="orders">
<id property="id" column="oid"/>
<result property="userId" column="id"/>
<result property="number" column="number"/>
<result property="createtime" column="createtime"/>
<result property="note" column="note"/>
</collection>
</resultMap>
<select id="findUserAndOrderRstMap" resultMap="userAndOrderRstMap">
SELECT
u.*,
o.id oid,
o.number,
o.createtime,
o.note
FROM
`user` u
LEFT JOIN orders o ON u.id = o.user_id
</select>
Collection 标签:定义了一对多关联的结果映射。
property=“orders”:关联查询的结果集存储在 User 对象的上哪个属性。
ofType=“orders”:指定关联查询的结果集中的对象类型即 List 中的对象类型。此处可以使用别名,也可以使用全限定名。
Mapper 接口
// resultMap 入门
public List<User> findUserAndOrdersRstMap() throws Exception;
测试代码
@Test
public void testFindUserAndOrdersRstMap() {SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> result = userMapper.findUserAndOrdersRstMap();
for (User user : result) {System.out.println(user);
}
session.close();}
延迟加载
什么是延迟加载
- MyBatis 中的延迟加载,也称为 懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的 select 查询。延迟加载可以有效的减少数据库压力。
- Mybatis 的延迟加载,需要通过 resultMap 标签中的 association 和 collection 子标签才能演示成功。
- Mybatis 的延迟加载,也被称为是嵌套查询,对应的还有 嵌套结果 的概念,可以参考一对多关联的案例。
- 注意:MyBatis 的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。
延迟加载的分类
MyBatis 根据对关联对象查询的 select 语句的 执行时机 ,分为三种类型: 直接加载、侵入式加载与深度延迟加载
- 直接加载: 执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
- 侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的某个属性(该属性不是关联对象的属性)时,就会马上执行关联对象的 select 查询。
- 深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的 select 查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。
延迟加载策略需要在 Mybatis 的全局配置文件中,通过 <settings> 标签进行设置。
案例准备
查询订单信息及它的下单用户信息。
直接加载
通过对全局参数:lazyLoadingEnabled 进行设置,默认就是 false。
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="false"/>
</settings>
侵入式延迟加载
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
深度延迟加载
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
N+ 1 问题
- 深度延迟加载的使用会提升性能。
- 如果延迟加载的表数据太多,此时会产生 N + 1 问题,主信息加载一次算 1 次,而从信息是会根据主信息传递过来的条件,去查询从表多次。
动态 SQL
动态 SQL 的思想:就是使用不同的动态 SQL 标签去完成字符串的拼接处理、循环判断。
解决的问题是:
1、在映射文件中,会编写很多有重叠部分的 SQL 语句,比如 SELECT 语句和 WHERE 语句等这些重叠语句,该如何处理
2、SQL 语句中的 where 条件有多个,但是页面只传递过来一个条件参数,此时会发生问题。
if 标签
综合查询的案例中,查询条件是由页面传入,页面中的查询条件可能输入用户名称,也可能不输入用户名称。
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user where 1=1
<if test="user != null">
<if test="user.username != null and user.username !=''">
AND username like '%${user.username}%'
</if>
</if>
</select>
注意:要做『不等于空』字符串校验。
where 标签
上边的 sql 中的 1 =1,虽然可以保证 sql 语句的完整性:但是存在性能问题。Mybatis 提供 where 标签解决该问题。
代码修改如下:
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user
<!-- where 标签会处理它后面的第一个 and -->
<where>
<if test="user != null">
<if test="user.username != null and user.username !=''">
AND username like '%${user.username}%'
</if>
</if>
</where>
</select>
sql 片段
在映射文件中可使用 sql 标签将重复的 sql 提取出来,然后使用 include 标签引用即可,最终达到 sql 重用的目的,具体实现如下:
-
原映射文件中的代码:
<select id="findUserList" parameterType="queryVo" resultType="user"> SELECT * FROM user <!-- where 标签会处理它后面的第一个 and --> <where> <if test="user != null"> <if test="user.username != null and user.username !=''"> AND username like '%${user.username}%' </if> </if> </where> </select>
- 将 where 条件抽取出来:
<sql id="query_user_where">
<if test="user != null">
<if test="user.username != null and user.username !=''">
AND username like '%${user.username}%'
</if>
</if>
</sql>
- 使用 include 引用:
<!-- 使用包装类型查询用户 使用 ognl 从对象中取属性值,如果是包装对象可以使用. 操作符来取内容部的属性 -->
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user
<!-- where 标签会处理它后面的第一个 and -->
<where>
<include refid="query_user_where"></include>
</where>
</select>
注意:
1、如果引用其它 mapper.xml 的 sql 片段,则在引用时需要加上 namespace,如下:
<include refid="namespace.sql 片段”/>
foreach
需求
综合查询时,传入多个 id 查询用户信息,用下边两个 sql 实现:
SELECT * FROM USER WHERE username LIKE '% 老郭 %' AND (id =1 OR id =10 OR id=16)
SELECT * FROM USER WHERE username LIKE '% 老郭 %' AND id IN (1,10,16)
POJO
在 pojo 中定义 list 属性 ids 存储多个用户 id,并添加 getter/setter 方法
Mapper 映射文件
<sql id="query_user_where">
<if test="user != null">
<if test="user.username != null and user.username !=''">
AND username like '%${user.username}%'
</if>
</if>
<if test="ids != null and ids.size() > 0">
<!-- collection:指定输入的集合参数的参数名称 -->
<!-- item:声明集合参数中的元素变量名 -->
<!-- open:集合遍历时,需要拼接到遍历 sql 语句的前面 -->
<!-- close:集合遍历时,需要拼接到遍历 sql 语句的后面 -->
<!-- separator:集合遍历时,需要拼接到遍历 sql 语句之间的分隔符号 -->
<foreach collection="ids" item="id" open="AND id IN ("
close=")" separator=",">
#{id}
</foreach>
</if>
</sql>
测试代码
在 UserMapperTest 测试代码中,修改 testFindUserList 方法,如下:
@Test
public void testFindUserList() throws Exception {SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得 mapper 的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建 QueryVo 对象
QueryVo queryVo = new QueryVo();
// 创建 user 对象
User user = new User();
user.setUsername("老郭");
queryVo.setUser(user);
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);// 查询 id 为 1 的用户
ids.add(10); // 查询 id 为 10 的用户
queryVo.setIds(ids);
// 根据 queryvo 查询用户
List<User> list = userMapper.findUserList(queryVo);
System.out.println(list);
sqlSession.close();}
注意事项
如果 parameterType 不是 POJO 类型,而是 List 或者 Array 的话,那么 foreach 语句中,collection 属性值需要固定写死为 list 或者 array。
作业
编写批量删除的 select 标签,parameterType 指定为 list
注意:foreach 标签应该怎么写?
Mybatis 缓存
缓存介绍
Mybatis 提供 查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis 的查询 缓存总共有两级,我们称之为一级缓存和二级缓存,如图:
- 一级缓存是 SqlSession 级别 的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。
- 二级缓存是 Mapper(namespace)级别的缓存。多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
一级缓存
Mybatis 默认开启了一级缓存
原理图
说明:
- 第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。
- 如果中间 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
测试 1
@Test
public void testOneLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询 ID 为 1 的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询 ID 为 1 的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();}
测试 2
@Test
public void testOneLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询 ID 为 1 的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("隔壁老詹 1");
user.setAddress("洛杉矶湖人");
// 执行增删改操作,清空缓存
mapper.insertUser(user);
// 第二次查询 ID 为 1 的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();}
具体应用
正式开发,是将 mybatis 和 spring 进行整合开发,事务控制在 service 中。
一个 service 方法中包括 很多 mapper 方法调用:
service{
// 开始执行时,开启事务,创建 SqlSession 对象
// 第一次调用 mapper 的方法 findUserById(1)
// 第二次调用 mapper 的方法 findUserById(1),从一级缓存中取数据
// 方法结束,sqlSession 关闭
}
如果是执行两次 service 调用查询相同 的用户信息,是不走一级缓存的,因为 mapper 方法结束,sqlSession 就关闭,一级缓存就清空。
二级缓存
原理
二级缓存是 mapper(namespace)级别的。
下图是多个 sqlSession 请求 UserMapper 的二级缓存图解。
说明:
- 第一次调用 mapper 下的 SQL 去查询用户信息。查询到的信息会存到该 mapper 对应的 二级缓存区域 内。
- 第二次调用相同 namespace 下的 mapper 映射文件中相同的 SQL 去查询用户信息。会去对应的二级缓存内取结果。
- 如果调用相同 namespace 下的 mapper 映射文件中的增删改 SQL,并执行了 commit 操作。此时会清空该 namespace 下的二级缓存。
开启二级缓存
Mybatis 默认是没有开启二级缓存,开启步骤如下:
- 在核心配置文件 SqlMapConfig.xml 中加入以下内容(开启二级缓存总开关):
<!-- 开启二级缓存总开关 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在 UserMapper 映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本 mapper 下的 namespace 的二级缓存,默认使用的是 mybatis 提供的 PerpetualCache -->
<cache></cache>
实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,比如说存储到文件系统中,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。
测试 1
@Test
public void testTwoLevelCache() {SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询 ID 为 1 的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭 SqlSession1
sqlSession1.close();
// 第二次查询 ID 为 1 的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭 SqlSession2
sqlSession2.close();}
Cache Hit Radio:缓存命中率
第一次缓存中没有记录,则命中率 0.0;
第二次缓存中有记录,则命中率 0.5(访问两次,有一次命中)
测试 2
@Test
public void testTwoLevelCache() {SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询 ID 为 1 的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭 SqlSession1
sqlSession1.close();
// 修改查询出来的 user1 对象,作为插入语句的参数
user1.setUsername("隔壁老詹 1");
user1.setAddress("洛杉矶湖人");
mapper3.insertUser(user1);
// 提交事务
sqlSession3.commit();
// 关闭 SqlSession3
sqlSession3.close();
// 第二次查询 ID 为 1 的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭 SqlSession2
sqlSession2.close();}
禁用二级缓存
默认二级缓存的粒度是 Mapper 级别的,但是如果在同一个 Mapper 文件中某个查询不想使用二级缓存的话,就需要对缓存的控制粒度更细。
在 select 标签中设置 useCache=false,可以禁用当前 select 语句的二级缓存,即每次查询都是去数据库中查询, 默认情况下是 true,即该 statement 使用二级缓存。
<select id="findUserById" parameterType="int" resultType="com.kkb.mybatis.po.User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
刷新二级缓存
通过 flushCache 属性,可以控制 select、insert、update、delete 标签是否属性二级缓存
默认设置
* 默认情况下如果是 select 语句,那么 flushCache 是 false。
* 如果是 insert、update、delete 语句,那么 flushCache 是 true。
默认配置解读
* 如果查询语句设置成 true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
* 如果增删改语句设置成 false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache 设置如下:
<select id="findUserById" parameterType="int"
resultType="com.kkb.mybatis.po.User" useCache="true" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
应用场景
- 使用场景:
对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
- 注意事项:
在使用二级缓存的时候,要设置一下 刷新间隔 (cache 标签中有一个flashInterval 属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置 30 分钟、60 分钟等,单位为毫秒。
局限性
Mybatis 二级缓存对细粒度的数据级别的缓存实现不好。
- 场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是 mapper 级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
- 解决方法
此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个 namespace 的 mapper 中。
Mybatis 逆向工程
逆向工程介绍
使用官方网站的 Mapper 自动生成工具 mybatis-generator-core-1.3.2 来针对单表生成 po 类(Example)和 Mapper 接口和 mapper 映射文件
修改配置文件
在 generatorConfig.xml 中配置 Mapper 生成的详细信息,注意修改以下几点:
1. 修改要生成的数据库表
2. pojo 文件所在包路径
3. Mapper 所在的包路径
配置文件如下:
<?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/ssm" userId="root" password="root">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg" password="yycg"> </jdbcConnection> -->
<!-- 默认 false,把 JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true 时把 JDBC DECIMAL
和 NUMERIC 类型解析为 java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject: 生成 PO 类的位置 -->
<javaModelGenerator targetPackage="com.kkb.ms.po"
targetProject=".\src">
<!-- enableSubPackages: 是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper 映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.kkb.ms.mapper"
targetProject=".\src">
<!-- enableSubPackages: 是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper 接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.kkb.ms.mapper" targetProject=".\src">
<!-- enableSubPackages: 是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table schema=""tableName="user"></table>
<table schema=""tableName="order"></table>
</context>
</generatorConfiguration>
注意事项
每次执行逆向工程代码之前,先 删除原来已经生成的 mapper xml 文件再进行生成。
- mapper.xml文件的内容不是被覆盖而是进行 内容追加,会导致 mybatis 解析失败。
- po 类及 mapper.java 文件的内容是直接覆盖没有此问题。
PageHelper 分页插件
PageHelper 分页插件介绍
https://github.com/pagehelper…
* 如果你也在用 Mybatis,建议尝试该分页插件,这个一定是 最方便 使用的分页插件。
* 目前几乎支持所有的关系型数据库
* 最新版本是 5.1.6。
使用方法
添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
配置 PageHelper
- Mybatis 全局配置文件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- config params as the following -->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
- spring 配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- other configuration -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!-- config params as the following -->
<value>
helperDialect=mysql
</value>
</property>
</bean>
</array>
</property>
</bean>
项目中使用 PageHelper
// 获取第 1 页,10 条内容,默认查询总数 count
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectAll();
// 用 PageInfo 对结果进行包装
PageInfo page = new PageInfo(list);
// 测试 PageInfo 全部属性
//PageInfo 包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
注意事项
- 需要分页的查询语句,必须是处于 PageHelper.startPage(1, 10); 后面的第一条语句。
- 如果查询语句是使用 resultMap 进行的嵌套结果映射,则无法使用 PageHelper 进行分页。
扩展点
- Mybatis Plus
总结
作业
- 使用 resultType 完成一对一的结果映射。
- foreach 动态标签的使用:通过 List 集合作为参数传递 id 集合。
下节预告
Mybatis 架构分析及手写 Mybatis 框架。