第一章 MyBatis简介
1.1 数据库知识回顾和总结
各种持久层方案对比
-
JDBC
-
SQL夹在Java代码块里,耦合度高导致硬编码内伤
-
维护不易且实际开发需求中sql有变化,频繁修改的情况多见
-
-
JDBCTemplate
-
Spring框架中对于数据库操作的模板对象,但是结果集还是需要程序员手动处理
-
结果集中的一对一,一对多,多对多关系也需要程序员编码处理
-
SQL语句在JAVA代码中,与JAVA代码的耦合度高(SQL语句转移到配置文件中)
-
-
Hibernate和JPA
-
长难复杂SQL,对于Hibernate而言处理也不容易
-
内部自动生产的SQL,不容易做特殊优化
-
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降
-
-
MyBatis
-
对开发人员而言,核心sql还是需要自己优化
-
sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据
-
1.2 MyBatis历史
MyBatis是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github。
iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis 提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
1.3 MyBatis特点
-
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
-
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
-
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录
-
Mybatis 是一个 半自动的ORM(Object Relation Mapping)框架
1.4 MyBatis下载
下载网址GitHub - mybatis/mybatis-3: MyBatis SQL mapper framework for Java
MyBatis在线中文帮助文档 mybatis – MyBatis 3 | 简介
第二章 MyBatis入门
2.1 MyBatis执行流程简介和配置文件介绍
2.2 MyBatis项目搭建
准备插件
环境设置
导入依赖
<!--导入MySQL的驱动包--> <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.6</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
准备log4j.xml或者log4j.properties
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout> </appender> <logger name="java.sql"> <level value="debug" /> </logger> <logger name="org.apache.ibatis"> <level value="info" /> </logger> <root> <level value="debug" /> <appender-ref ref="STDOUT" /> </root> </log4j:configuration>
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:/yongbo.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
创建数据库表格
执行mydb.sql脚本或者直接在mysql前端工具中运行如下脚本
CREATE TABLE `department` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `department` VALUES (1, '教学部'); INSERT INTO `department` VALUES (2, '教务部'); INSERT INTO `department` VALUES (3, '运营部'); INSERT INTO `department` VALUES (4, '咨询部'); INSERT INTO `department` VALUES (5, '就业部'); CREATE TABLE `employee` ( `id` int(11) NOT NULL AUTO_INCREMENT, `last_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `email` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `salary` double(11, 2) NULL DEFAULT NULL, `dept_id` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `dept_id`(`dept_id`) USING BTREE, CONSTRAINT `employee_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `department` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `employee` VALUES (1, 'Mark', 'Mark@yongbo.com', 30000.00, 1); INSERT INTO `employee` VALUES (2, 'Mayun', 'mayun@alibaba.com', 20000.00, 2); INSERT INTO `employee` VALUES (3, 'Mahuateng', 'mahuateng@qq.com', 10000.00, 3); SET FOREIGN_KEY_CHECKS = 1;
准备项目包结构
-
实体类包 com.yongbo.entities /com.yongbo.pojo
-
mapper接口和映射包 com.atguitu.mapper(其实就是原来的DAO层)
准备实体类
-
Employee
@AllArgsConstructor @NoArgsConstructor @Data public class Employee implements Serializable { private Integer id; private String last_name; private String email; private Double salary; private Integer dept_id; }
-
Department
@Data @AllArgsConstructor @NoArgsConstructor public class Department { private Integer id; private String name; }
2.3 MyBatis全局配置文件
resources目录下添加如下配置文件,参照 从 XML 中构建 SqlSessionFactory下提供的模板
-
位置截图:
<?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.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.6.101:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <!--加载Mapper映射文件--> <mapper resource="com/yongbo/mapper/EmployeeMapper.xml"/> </mappers> </configuration>
2.4 Mybatis的Mapper映射文件
resources目录下添加如下配置文件
-
位置截图:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace : 目前没有明确的要求 在基于接口的代理模式下,必须是对应接口的全路径名 多个mapper.xml映射问文件的namespace不能相同 mapper 标签作为根标签, 内部定义的是所有的CURD的SQL语句 --> <mapper namespace="EmployeeMapper"> <!-- id SQL语句id 运行SQL语句时,找SQL的标志 目前该id没有 明确要求,见名知意即可,后续他必须是对应的接口中的方法名 在同一个mapper.xml中,id不能重复 resultType 用来定义结果集转换封装所对应色实体类,并非和返回的结果完全一致 List 只有查询才有该属性 --> <select id="findAll" resultType="com.yongbo.mybatis.pojo.Employee"> select * from employee </select> <!--增加--> <insert id="addEmp"> insert into employee values (DEFAULT ,'按住啦baby','zhaosi@yuzhibo.com','1002.34',1) </insert> <!--修改--> <update id="updateEmp" > update employee set last_name ='旋涡刘能',email= '刘能@旋涡.com',salary =1234.456,dept_id = 2 where id =5 </update> <!--删除--> <delete id="deleteEmp"> delete from employee where id = 5 </delete> </mapper>
2.5 MyBatis测试代码
test/目录下创建TestEmployeeMapper类
-
具体测试代码如下
package com.yongbo.test; import com.yongbo.mybatis.pojo.Employee; 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 TestSqlSession { private SqlSession sqlSession; /*初始化SQLsession*/ @Before public void init() throws IOException { SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); // 通过MyBatis提供的工具类获取制定位置 的输入流 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis.xml"); // SqlSessionFactoryBuilder构建SqlSessionFactory SqlSessionFactory sqlSessionFactory =ssfb.build(resourceAsStream); // SqlSessionFactory获取SQLsession 对象 sqlSession=sqlSessionFactory.openSession(); } @Test public void testSelectList() throws IOException { List<Employee> employees = sqlSession.selectList("findAll"); for (Employee employee : employees) { System.out.println(employee); } } @Test public void testInsert() throws IOException { /*增删改需要提交事务 * 方式1 sqlSession=sqlSessionFactory.openSession(true); * 方式2 sqlSession.commit() * */ int rows = sqlSession.insert("addEmp"); sqlSession.commit(); System.out.println(rows); } @Test public void testUpdate(){ int rows = sqlSession.update("updateEmp"); sqlSession.commit(); System.out.println(rows); } @Test public void testDelete(){ int rows =sqlSession.delete("deleteEmp"); sqlSession.commit(); System.out.println(rows); } /*关闭SQL session*/ @After public void close(){ sqlSession.close(); } }
2.6 MyBatis基于接口代理模式的开发
2.6.1 问题分析
-
直接通过SqlSession对象的API实现CURD编码比较繁琐
-
持久层没有接口,不利于后续的降耦和统一设计规范
-
参数传递编码比较繁琐
2.6.2 基于接口的代理模式开发
项目结构截图
准备Employee接口
package com.yongbo.mapper; import com.yongbo.pojo.Employee; import java.util.List; public interface EmployeeMapper { List<Employee> findAll(); }
准备EmployeeMapper.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="com.yongbo.mapper.EmployeeMapper"> <!-- namespace 必须是接口的全限定名 resultType 指定结果集封装使用的实体类 id SQL语句的id --> <select id="findAll" resultType="com.yongbo.pojo.Employee"> select * from employee </select> </mapper>
测试代码
/** * 测试getMapper方法 * @throws IOException */ @Test public void testGetMapper() throws IOException { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); List<Employee> emps = mapper.findAll(); emps.forEach(System.out::println); }
注意以下几点相同!!!
-
Mapper接口和对应的Mapperxml文件应该在编译之后生成在同一个目录下(后面整合之后可以不同)
-
Mapper接口的对应的Mapperxml文件应该同名(不包含拓展名)
-
Mapper接口和全限定名应该和对应的Mapperxml中的namespace保持一致
-
Mapper接口中定义的方法名应该和对应的SQL语句的ID保持一致
核心配置文件中加载EmployeeMapper.xml
<mapper resource="com/yongbo/mapper/EmployeeMapper.xml"/>
第三章 MyBatis全局配置文件解读
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
3.1 properties 属性标签
可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来配置
<properties resource="jdbc.properties"> <!--如果 resource 读取的文件中和property属性中配置的键一致了,配置文件中的优先 --> <property name="jdbc.username" value="root"/> </properties>
然而properties的作用并不单单是这样,你可以创建一个资源文件,名为jdbc.properties的文件,将四个连接字符串的数据在资源文件中通过键值 对(key=value)的方式放置,不要任何符号,一条占一行
-
resources目录下创建jdbc.properties文件,内容如下
# mysql5.7 jdbc.url=jdbc:mysql://192.168.6.101:3306/mydb jdbc.driver=com.mysql.jdbc.Driver jdbc.username=root jdbc.password=root # mysql8 mysql8.url=jdbc:mysql://192.168.6.101:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true mysql8.driver=com.mysql.cj.jdbc.Driver mysql8.username=root mysql8.password=root
-
全局配置文件通过properties标签引入jdbc.properties文件
<!-- resource属性:引入类路径下的属性文件 url属性:引入网络或磁盘路径下的属性文件 --> <properties resource="jdbc.properties"></properties>
-
environment元素中,使用${}来动态引用jdbc.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>
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
-
首先读取在 properties 元素体内指定的属性。
-
然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
-
最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
3.2 settings 设定标签
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项常见设置的含义、默认值等
设置名 | 描述 | 默认值 |
---|---|---|
cacheEnabled | 缓存的全局开关 | true |
lazyLoadingEnabled | 延迟加载的全局开关 | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载 | false (在 3.4.1 及之前的版本中默认为 true) |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | partial |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn | false |
比较完整的settings设置
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
3.3 typeAliases 别名标签
通过子标签typeAlias设置单个别名
<typeAliases> <!-- 子标签typeAlias:用来给某些类指定别名 type属性:指定起别名的类的全类名 alias属性:指定别名,如果没有指定则是类命的首字母小写,但是别名大小写不敏感 --> <typeAlias type="com.yongbo.mybatis.entities.Employee" alias="employee"></typeAlias> </typeAliases>
通过package包扫描批量设置别名
<typeAliases> <!-- 子标签package:通过指定包名给包下所有的类起别名 name属性:指定包名 --> <package name="com.yongbo.mybatis.entities"/> </typeAliases>
MyBatista的内建别名 已经取好的别名
3.4 typeHandlers 类型处理器标签
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型
MyBatis中提供的类型处理器:
日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了.日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。 MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的,如需注册,需要下载mybatis-typehandlers-jsr310,并通过如下方式注册
自定义类型转换处理器,我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型,步骤如下
-
实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
-
指定其映射某个JDBC类型(可选操作)
-
在mybatis全局配置文件中注册
3.5 plugins 插件机制标签
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行
MyBatis四大对象:
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
-
ParameterHandler (getParameterObject, setParameters)
-
ResultSetHandler (handleResultSets, handleOutputParameters)
-
StatementHandler (prepare, parameterize, batch, update, query)
3.6 environments环境配置标签
MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置
每种环境使用一个environment标签进行配置并通过id属性指定唯一标识符
可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
environment-指定具体环境
-
id:指定当前环境的唯一标识
-
transactionManager和dataSource都必须有
<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> <!--生产环境--> <environment id="online"> <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>
transactionManager
-
type: JDBC | MANAGED | 自定义
-
JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范 围。 JdbcTransactionFactory
-
MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory
-
自定义:实现TransactionFactory接口,type=全类名/别名
-
dataSource
-
type: UNPOOLED | POOLED | JNDI | 自定义
-
UNPOOLED:不使用连接池, UnpooledDataSourceFactory
-
POOLED:使用连接池, PooledDataSourceFactory
-
JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
-
自定义:实现DataSourceFactory接口,定义数据源的获取方式。
-
可以通过org.apache.ibatis.session. Configuration查询上述信息
实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
3.7databaseIdProvider数据库厂商标识标签
MyBatis 可以根据不同的数据库厂商执行不同的语句
<!--设置数据库厂商标识--> <databaseIdProvider type="DB_VENDOR"> <property name="MySQL" value="mysql"/> <property name="Oracle" value="oracle"/> </databaseIdProvider>
type: DB_VENDOR, 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。VendorDatabaseIdProvider会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短。
-
name:数据库厂商标识
-
value:为标识起一个别名,方便SQL语句使用databaseId属性引用
配置了databaseIdProvider后,在SQL映射文件中的增删改查标签中使用databaseId来指定数据库标识的别名
<select id="getEmployeeById" resultType="employee" databaseId="mysql"> select id,last_name,email,salary,dept_id from employees where id = 1 </select>
MyBatis数据库厂商匹配规则如下:
-
如果没有配置databaseIdProvider标签,那么databaseId=null
-
如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上则设置databaseId=配置指定的值,否则依旧为null
-
如果databaseId不为null,他只会执行配置databaseId的sql语句
-
MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带databaseId 的相同语句,则后者会被舍弃。
3.8 mappers映射器标签
用来在mybatis初始化的时候,告诉mybatis需要引入哪些Mapper映射文件
mapper标签:逐个注册SQL映射文件
-
resource属性:引入类路径下的文件
-
url 属性:引入网络路径或者是磁盘路径下的文件
-
class属性:设置Mapper接口的全类名
-
如果有SQL映射文件,要求Mapper接口与 SQL映射文件同名同包。
-
如果没有SQL映射文件 ,使用注解在接口的方法上写SQL语句。
-
<mappers> <mapper resource="com/yongbo/mapper/EmployeeMapper.xml"/> </mappers>
package标签:批量注册SQL映射文件
-
name 属性:设置Mapper接口所在的包名
-
如果有SQL映射文件 ,要求Mapper接口与 SQL映射文件同名同包。
-
如果没有SQL映射文件,使用注解在接口的方法上写SQL语句
-
<mappers> <package name="com.yongbo.mapper"/> </mappers>
第四章 MyBatis映射文件解读
4.1 Mapper映射文件简介
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
-
cache – 该命名空间的缓存配置。
-
cache-ref – 引用其它命名空间的缓存配置。
-
resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
-
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
-
sql – 可被其它语句引用的可重用语句块。
-
insert – 映射插入语句。
-
update – 映射更新语句。
-
delete – 映射删除语句。
-
select – 映射查询语句。
4.2 Mapper映射文件实现CURD
4.2.1 select
Mapper接口
Employee getEmployeeById();
Mapper映射文件
<select id="getEmployeeById" resultType="employee" databaseId="mysql"> select id,last_name,email,salary,dept_id from employees where id = 1 </select>
select 标签属性说明
-
id:指定接口中的方法名。
-
parameterType:设置传入的参数的类型,也可以不指定,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数。
-
resultType:设置方法的返回值的类型。注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
-
resultMap:设置对外部 resultMap高级结果集标签的引用。
-
flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
-
useCache:将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
-
databaseId:如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
4.2.2 insert
Mapper 接口方法
void addEmployee();
Mapper映射文件
<!-- parameterType属性:设置请求参数的类型的全类名,该属性也可以不指定, MyBatis 可以通过类型处理器推断出具体传入语句的参数 --> <insert id="addEmployee" parameterType="com.yongbo.mybatis.entities.Employee"> insert into employees(last_name,email,salary,dept_id) values('按住啦baby','baby@yongbo.cn',20000.00,1) </insert>
4.2.3 update
Mapper接口方法
void updateEmployee();
Mapper映射文件
<update id="updateEmployee"> update employee set last_name='云龙',email ='yunlong@yidali.cn',salary=200000.0,dept_id=2 where id =5 </update>
4.2.4 delete
Mapper接口方法
void deleteEmployeeById();
Mapper映射文件
<delete id="deleteEmployeeById"> delete from employees where id = 5 </delete>
insert、update、delete标签属性说明
-
flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
-
useGeneratedKeys:(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
-
keyProperty:(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
-
keyColumn:(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
Mapper接口中注解的使用
package com.yongbo.mapper; import com.yongbo.pojo.Employee; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; public interface EmployeeMapper { @Select(" select * from employee") List<Employee> findAll(); @Insert("insert into employee values(null,'按住啦baby','baby@yongbo.cn',20000.00,1)") int addEmp(); @Update("update employee set last_name='云龙',email ='yunlong@yidali.cn',salary=200000.0,dept_id=2 where id =5") int updateEmp(); @Delete("delete from employee where id =5") int deleteEmp(); }
4.3 Mapper参数传递问题
4.3.1 参数的传递方式
单个普通类型参数,可以接受基本类型,包装类型,字符串类型等。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。
-
Mapper接口
/** * 根据id查找单个员工信息 * @param id 要查找的员工id * @return 如果找到返回单个Employee对象,否则null */ Employee findEmployeeByID(int id);
-
Mapper.xml映射文件
<!--findEmployeeByID--> <!-- ${} #{} 都是参数的占位符 如果是单个基础数据类型作为入参,{}中可以随意写名字 $ 底层使用Statement语句对象 # 底层使用PreparedStatment --> <select id="findEmployeeByID" parameterType="int" resultType="employee"> select * from employee where id =#{id} </select>
多个普通数据类型参数. 任意多个参数,都会被MyBatis重新包装成一个Map传入。Map的key是param1,param2,或者arg0,arg1…,值就是参数的值。
-
Mapper接口
/** * 根据员工工号和姓名查询员工信息 * @param id 员工的工号 * @param lastName 员工的姓名 * @return 找到返回Employee单个对象,否则null */ Employee findEmployeeByIDAndLastName(@Param("id") int id, @Param("lastName") String lastName);
-
Mapper.xml映射文件
<!-- 两种默认名称 arg0 arg1 从0开始 param1 param2 从1开始 通过@Param注解指定参数的别名 通过@Param指定别名后,arg形式就不可以使用了 param形式仍然可用 多个基础数据类型作为方法参数时,mybatis会把多个参数转换成一个Map集合 参数名 key value id arg0/param1 1 lastname arg1/param2 Mark 当使用@Param注解之后,集合中的数据如下 参数名 key value id id/param1 1 lastname lastName/param2 Mark --> <select id="findEmployeeByIDAndLastName" parameterType="map" resultType="employee" > <!--select * from employee where id =#{arg0} and last_name=#{arg1}--> select * from employee where id =#{param1} and last_name=#{param2} <!--select * from employee where id =#{id} and last_name=#{lastName}--> </select>
POJO 当这些参数属于我们业务POJO时,我们直接传递POJO。
-
Mapper接口
/** * 增加新员工的方法 * @param employee 携带所有员工信息的对象,不包含id * @return 对于数据库记录改变行数 */ int addEmployee( Employee employee );
-
Mapper.xml映射文件
<!-- 如果是Pojo作为入参 #{}中写的是对象的属性名 --> <insert id="addEmployee" parameterType="employee" > insert into employee values(null,#{lastName},#{email},#{salary},#{deptId}) </insert>
Map .我们也可以封装多个参数为map
-
Mapper接口
/** * 增加员工信息 * @param params 员工的所有字段以Map集合形式传递,key为属性名 * @return 数据库记录改变行数 */ int addEmployeeMap(Map<String ,Object> params);
-
Mapper.xml映射文件
<!--int addEmployeeMap(Map<String ,Object> params);--> <insert id="addEmployeeMap" parameterType="map"> insert into employee values(DEFAULT,#{lastName},#{gender},#{hiredate},#{email},#{salary},#{deptId}) </insert>
-
测试代码
@Test public void testAddEmployeeMap(){ EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Map<String,Object> map=new HashMap<>(); map.put("lastName","小明"); map.put("gender",1); map.put("hiredate",new Date()); map.put("email","xiaoming@123.com"); map.put("salary",3000.00); map.put("deptId",2); int i = mapper.addEmployeeMap(map); System.out.println(i); }
多个引用类型作为方法参数
接口
/** * 增加员工信息 * @param empa 携带员工 lastName gender hiredate 三个属性 * @param empb 携带员工 email salary deptId 三个属性 * @return 数据库记录改变行数 */ int addEmployeeX(@Param("empa")Employee empa,@Param("empb")Employee empb);
映射文件
<!--int addEmployeeX(@Param("empa")Employee empa,@Param("empb")Employee empb); Map key value param1 empa param2 empb empa empa empb empb --> <insert id="addEmployeeX" parameterType="map"> insert into employee values(DEFAULT,#{param1.lastName},#{param1.gender},#{empa.hiredate},#{empb.email},#{empb.salary},#{param2.deptId}) </insert>
测试代码
@Test public void testAddEmployeeX(){ EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee empa =new Employee(); empa.setLastName("小明"); empa.setGender(1); empa.setHiredate(new Date()); Employee empb =new Employee(); empb.setEmail("xiaoming@123.com"); empb.setSalary(3000.00); empb.setDeptId(3); int i = mapper.addEmployeeX(empa,empb); System.out.println(i); }
Collection/Array/list 会被MyBatis封装成一个map传入, Collection对应的key是collection,Array对应的key是array. 如果确定是List集合,key还可以是list。
-
Mapper接口
见后面foreach标签
-
Mapper.xml映射文件
见后面foreach标签
4.3.2 参数的其他处理
参数位置支持的属性
javaType、jdbcType、mode、numericScale、resultMap、typeHandler、jdbcTypeName、expression
可能为空的列指定jdbcType
<insert id="addEmployee"> insert into oracle_employees(last_name,email,salary,dept_id) values(#{lastName,jdbcType=null},#{email},#{salary},#{deptId}) </insert>
${} 和#{} 的差别
-
#{key}:获取参数的值,预编译到SQL中。安全。
-
${key}:获取参数的值,拼接到SQL中。有SQL注入问题。
4.3.3 参数处理源码阅读
以命名参数为例:
Employee getEmployeeByLastNameAndEmail(@Param("lastName") String lastName , @Param("email") String email);
底层源码
a) 在MapperProxy<T>类 85 行和152行
b) 在MapperMethod类的59行
c) 在ParamNameResolver类的82行
4.4 Mapper主键生成方式和主键回填
主键生成方式
-
支持主键自增,例如MySQL数据库
-
不支持主键自增,例如Oracle数据库
获取主键回填
-
若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
<insert id="addEmployee" useGeneratedKeys="true" keyProperty="id"> insert into employees(last_name,email,salary,dept_id) values(#{lastName},#{email},#{salary},#{deptId}) </insert>
-
而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用。
<insert id="addEmployee" databaseId="oracle"> <selectKey order="BEFORE" keyProperty="id" resultType="integer"> select employee_seq.nextval from dual </selectKey> insert into oracle_employees(id,last_name,email,salary,dept_id) values(#{id},#{lastName},#{email},#{salary},#{deptId}) </insert>
或者
<insert id="addEmployee" databaseId="oracle"> <selectKey order="AFTER" keyProperty="id" resultType="integer"> select employee_seq.currval from dual </selectKey> insert into oracle_employees(id,last_name,email,salary,dept_id) values(employee_seq.nextval,#{lastName},#{email},#{salary},#{deptId}) </insert>
4.5 Mapper的几种查询方式
查询语句返回单个普通类型数据
int getEmployeeCount();
<!--getEmployeeCount--> <select id="getEmployeeCount" resultType="int"> select count(1) from employee </select>
查询单行数据返回单个对象
Employee findById(Integer id);
<select id="findById" resultType="employee"> select * from employee where id =#{id} </select>
查询多行数据返回对象集合
List<Employee> findAll();
<select id="findAll" resultType="employee" > select * from employee </select>
查询单行数据返回Map集合
-
数据库中的字段名为key
-
数据库中的字段值为value
Map<String,Object> getEmployeeByIdReturnMap(Integer id );
<select id="getEmployeeByIdReturnMap" resultType="map"> select * from employee where id =#{id} </select>
查询多行数据返回Map集合
@MapKey("id") //指定使用数据库中的那个字段的值作为map的key Map<Integer,Employee> getEmployeesReturnMap();
<select id="getEmployeesReturnMap" resultType="map" > select * from employee </select>
4.6 Mapper自动映射
resultType自动映射
-
autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是结果集列名和POJO属性名一致。
-
如果autoMappingBehavior设置为none则会取消自动映射。
-
数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMN,aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。
4.7 Mapper自定义映射
resultMap常用标签
-
resultMap,实现高级结果集映射的标签
-
id :resultMap子标签,用于完成主键值的映射
-
result :resultMap 子标签,用于完成普通列的映射
-
association :resultMap 子标签,一个复杂的类型关联;许多结果将包成这种类型,用于处理一对一关系
-
collection : resultMap 子标签,复杂类型的集合,用于处理一对多关系
4.7.1 resultMap的id和result中的属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number” |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样 |
javaType | 一个 Java 类的全限定名,或一个类型别名,如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。 |
jdbcType | JDBC 类型 |
typeHandler | 使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名 |
-
实体类
package com.yongbo.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor @Data public class Emp { private Integer eid; private String ename; private String email; private Double sal ; private Integer deptno; }
-
Mapper接口
package com.yongbo.mapper; import com.yongbo.pojo.Emp; import java.util.List; public interface EmpMapper { List<Emp> findAllEmp(); }
-
映射文件
<?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.yongbo.mapper.EmpMapper"> <!-- 使用resultMap 手动指定数据库字段和实体类属性之间的映射关系 type 用来指定最终用哪个实体类来映射结果接 column 数据库的列 property 实体类的属性 javaType 属性的数据类型 可省略 jdbcType 数据库字段的数据类型 可省略 --> <resultMap id="emp_employee" type="emp"> <id column="id" property="eid" ></id> <result column="last_name" property="ename" ></result> <result column="email" property="email" ></result> <result column="salary" property="sal" ></result> <result column="dept_id" property="deptno" ></result> </resultMap> <select id="findAllEmp" resultMap="emp_employee"> select * from employee </select> </mapper>
第五章 多表查询
5.1 一对一关联关系
5.1.1 association标签处理一对一关系
需求:根据员工编号查询单个员工信息,以及员工所在的部门信息,可以连接数据库查询两次
-
mapper接口
package com.yongbo.mapper; import com.yongbo.pojo.Employee; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; public interface EmployeeMapper { Employee findEmployeeByID(int id); }
package com.yongbo.mapper; import com.yongbo.pojo.Department; public interface DepartmentMapper { Department findDepartmentByID(int id); }
-
映射文件
<?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.yongbo.mapper.EmployeeMapper"> <select id="findEmployeeByID" resultType="employee"> select * from employee where id =#{id} </select> </mapper>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.yongbo.mapper.DepartmentMapper"> <select id="findDepartmentByID" resultType="department"> select * from department where id =#{id} </select> </mapper>
-
测试代码
@Test public void testFindEmpJoinDept(){ EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class); Employee employeeByID = employeeMapper.findEmployeeByID(1); System.out.println(employeeByID); Department departmentByID = departmentMapper.findDepartmentByID(employeeByID.getDeptId()); System.out.println(departmentByID); }
POJO中的属性可能会是一个对象,我们可以使用联合查询,并以级联属性的方式封装对象。使用association标签定义对象的封装规则。
-
修改Employee实体类
package com.yongbo.mybatis.entities; public class Employee { private Integer id; private String lastName; private String email; private Double salary; private Department department; }
-
准备Department实体类
package com.yongbo.mybatis.entities; public class Department { private Integer id; private String name; }
级联方式给部门属性赋值
-
Mapper接口方法
Employee findEmployeeByIdJoinDepartment(int id);
-
Mapper.xml映射代码
<resultMap id="empJoinDept" type="employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="salary" property="salary"></result> <result column="dept_id" property="deptId"></result> <result column="did" property="department.id"></result> <result column="name" property="department.name"></result> </resultMap> <select id="findEmployeeByIdJoinDepartment" resultMap="empJoinDept" > select e.*,d.id did ,d.name from employee e left join department d on e.dept_id = d.id where e.id =#{id} </select>
通过association标签给部门属性赋值
<resultMap id="empJoinDept" type="employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="salary" property="salary"></result> <result column="dept_id" property="deptId"></result> <!-- 通过association处理一对一级联关系 property 关联的属性名 javaType 属性所对应的数据类型 --> <association property="department" javaType="com.yongbo.pojo.Department"> <id column="did" property="id"></id> <result column="name" property="name"></result> </association> </resultMap> <select id="findEmployeeByIdJoinDepartment" resultMap="empJoinDept" > select e.*,d.id did ,d.name from employee e left join department d on e.dept_id = d.id where e.id =#{id} </select>
5.1.2 association标签分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是DAO层, 因此,对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
-
先通过员工的id查询员工信息
-
再通过查询出来的员工信息中的外键(部门id)查询对应的部门信息
-
将部门信息设置到员工中
创建DepartmentMapper接口和映射文件
-
DepartmentMapper接口
package com.yongbo.mapper; import com.yongbo.pojo.Department; public interface DepartmentMapper { Department findDepartmentByID(int id); }
-
DepartmentMapper.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="com.yongbo.mapper.DepartmentMapper"> <select id="findDepartmentByID" resultType="department"> select * from department where id =#{id} </select> </mapper>
在EmployeeMapper中添加一个方法
Employee getEmployeeAndDeptByStep(Integer id);
在EmployeeMapper.xml的映射文件中自定义高级映射实现分步查询
<resultMap id="empJoinDeptByStep" type="employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="salary" property="salary"></result> <result column="dept_id" property="deptId"></result> <!-- select 要调用的SQL语句 namespce+ sqlid colunm 外城的那一列作为select的参数传入 --> <association property="department" javaType="com.yongbo.pojo.Department" select="com.yongbo.mapper.DepartmentMapper.findDepartmentByID" column="dept_id" > </association> </resultMap> <select id="findEmpByIdJoinDeptByStep" resultMap="empJoinDeptByStep" > select * from employee where id=#{id} </select>
5.1.3 association标签分步查询使用延迟加载
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的settings中进行如下的配置:
<settings> <!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 关闭反延迟加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
在association标签中也可以设置延迟加载,将覆盖全局配置
<resultMap id="myEmp3" type="com.yongbo.mybatis.entities.Employee"> <id property="id" column="id"></id> <result property="lastName" column="last_name"></result> <result property="email" column="email"></result> <result property="salary" column="salary"></result> <!--通过association标签分步查询给部门属性赋值 property属性:指定Employee中部门的属性名 select属性:指定调用那个接口的那个方法查询部门信息 column属性:指定将那个字段的值传入到select中调用的方法中 fetchType属性:是否使用延迟加载(全局开关需要打开) lazy: 使用延迟加载 eager:关闭延迟加载 --> <association property="dept" select="com.yongbo.mybatis.dao.DepartmentMapper.getDepartmentById" column="dept_id" fetchType="lazy"></association> </resultMap>
5.2 一对多关联关系
5.2.1 collection标签处理一对多关系
需求: 根据部门号查询部门信息以及该部门下所有的员工信息
POJO中的属性可能会是一个集合对象,我们可以使用联合查询,并以级联属性的方式封装对象,使用collection标签定义对象的封装规则
-
实体类
public class Department { private Integer id; private String name; private List<Employee> emps; }
-
DepartmentMapper接口
Department findDeptJoinEmps(int id);
-
DeptartmentMapper映射文件
<resultMap id="deptJoinEmps" type="com.yongbo.pojo.Department"> <id column="did" property="id"></id> <result column="name" property="name"></result> <!-- collection 一对多关联时 给集合属性赋值的标签 property 对应的集合属性 ofType 集合中的元素的数据类型 --> <collection property="employees" ofType="com.yongbo.pojo.Employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="salary" property="salary"></result> <result column="dept_id" property="deptId"></result> </collection> </resultMap> <select id="findDeptJoinEmps" resultMap="deptJoinEmps"> select d.id did, d.name ,e.* from department d left join employee e on d.id =e.dept_id where d.id =#{id}; </select>
5.2.2 collection标签分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是DAO层, 因此,对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,就可以通过分步的方式完成查询。
-
先通过部门的id查询部门信息
-
再通过部门id作为员工的外键查询对应的员工信息.。
-
将所有员工设置到部门中
在EmployeeMapper中添加一个方法
List<Employee> getEmployeesByDeptId(Integer deptId);
在EmployeeMapper.xml文件中添加映射信息
<select id="getEmployeesByDeptId" resultType="com.yongbo.mybatis.entities.Employee"> select id,last_name,email,salary,dept_id from employees where dept_id = #{deptId} </select>
在DepartmentMapper中添加一个方法
Department getDepartmentAndEmpsByStep(Integer id);
在DepartmentMapper.xml的映射文件中自定义高级映射实现分步查询
<resultMap id="deptJoinEmpsByStep" type="com.yongbo.pojo.Department"> <id column="id" property="id"></id> <result column="name" property="name"></result> <!-- collection 一对多关联时 给集合属性赋值的标签 property 对应的集合属性 ofType 集合中的元素的数据类型 --> <collection property="employees" ofType="com.yongbo.pojo.Employee" select="com.yongbo.mapper.EmployeeMapper.getEmployeesByDeptId" column="id" fetchType="lazy" > </collection> </resultMap> <select id="findDeptJoinEmpsByStep" resultMap="deptJoinEmpsByStep"> select * from department where id =#{id} </select>
5.2.3 collection 分步查询使用延迟加载
<collection property="emps" select="com.yongbo.mapper.EmployeeMapper.getEmployeesByDeptId" column="id" fetchType="lazy"></collection>
5.2.4 扩展: 分步查询多列值的传递
如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成
Map来进行传递,语法如下: {k1=v1, k2=v2....}
在所调用的查询方法取值时就要参考Map的取值方式,需要严格的按照封装map时所用的key来取值.
column="{key1=column1,key2=column2}"
5.2.5 扩展:多对多关联关系查询
业务说明
根据学生id查询学生信息及学生所选的课程信息
准备数据库
实体类准备
@AllArgsConstructor @NoArgsConstructor @Data public class Course implements Serializable { private Integer cid; private String cname; } @AllArgsConstructor @NoArgsConstructor @Data public class Record implements Serializable { private Integer stuid; private Integer cid; // 关联一个课程 private Course course; } @AllArgsConstructor @NoArgsConstructor @Data public class Student implements Serializable { private Integer stuid; private String stuname; // 关联多个记录 private List<Record> records; }
接口代码
public interface StudentMapper { Student findStudentJoinCoursesByStuid(int stuid); }
xml映射文件代码
<mapper namespace="com.yongbo.mybatis.mapper.StudentMapper"> <!-- Student findStudentJoinCoursesByStuid(int stuid); --> <resultMap id="studentJoinCourses" type="student" > <id column="stuid" property="stuid"></id> <result column="stuname" property="stuname"></result> <collection property="records" ofType="record"> <id column="stuid" property="stuid"></id> <id column="cid" property="cid"></id> <association property="course" javaType="course"> <id column="cid" property="cid"></id> <result column="cname" property="cname"></result> </association> </collection> </resultMap> <select id="findStudentJoinCoursesByStuid" resultMap="studentJoinCourses" > select * from student s left join record r on s.stuid= r.stuid left join course c on r.cid = c.cid where s.stuid= #{stuid} </select> </mapper>
测试代码
package com.yongbo.mybatis.test; import com.yongbo.mybatis.mapper.StudentMapper; import com.yongbo.mybatis.pojo.Student; 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; public class TestStudentMapper { private SqlSession sqlSession; /*初始化SQLsession*/ @Before public void init() throws IOException { SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); // 通过MyBatis提供的工具类获取制定位置 的输入流 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis.xml"); // SqlSessionFactoryBuilder构建SqlSessionFactory SqlSessionFactory sqlSessionFactory =ssfb.build(resourceAsStream); // SqlSessionFactory获取SQLsession 对象 sqlSession=sqlSessionFactory.openSession(); } /* * 根据学生id查找单个学生信息,以及学生选的所有课程信息 * 1 编码方式分步查询 * 2 配置方式实现多对多关联查询 * 3 配置方式实现多对多分步查询 * */ @Test public void testFindStudentJoinCoursesByStuid(){ StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); Student student = mapper.findStudentJoinCoursesByStuid(1); System.out.println(student); } /*关闭SQL session*/ @After public void close(){ sqlSession.commit(); sqlSession.close(); } }
第六章 MyBatis的动态SQL
6.1 动态SQL简介
-
动态 SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作
-
动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似
-
MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作
if choose (when, otherwise) trim (where, set) foreach
-
OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等
-
访问对象属性: person.name
-
调用方法: person.getName()
-
调用静态属性/方法: @java.lang.Math@PI @java.util.UUID@randomUUID()
-
调用构造方法: new com.yongbo.bean.Person(‘admin’).name
-
运算符: +,-*,/,%
-
逻辑运算符: in,not in,>,>=,<,<=,==,!=
-
注意:xml中特殊符号如 ” , > , < 等这些都需要使用转义字符
-
6.2 if和where标签
1 根据员工编号查询员工信息
2 根据员工编号和员工姓名查询员工信息
3 根据员工邮箱查询员工信息
4 根据员工邮箱和工资查询员工信息
-
Mapper接口
List<Employee> findByCondition(Employee employee);
-
if用于完成简单的判断.
-
where用于解决SQL语句中where关键字以及条件前面的and或者or的问题
<select id="findByCondition" resultType="com.yongbo.mapper.Employee"> select id,last_name,email,salary from employees <where> <if test="id!=null"> id=#{id} </if> <if test="lastName!=null&&lastName!="""> and last_name=#{lastName} </if> <if test="email!=null and email.trim()!=''"> and email=#{email} </if> <if test="salary!=null"> and salary=#{salary} </if> </where> </select>
6.3 choose(when、otherwise) 标签
-
choose 主要是用于分支判断,类似于java中的switch case,只会满足所有分支中的一个
<select id="getEmployeeByConditionChoose" resultType="com.yongbo.mapper.Employee"> select id,last_name,email,salary from employees <where> <choose> <when test="id!=null"> id = #{id} </when> <when test="lastName!=null"> last_name = #{lastName} </when> <when test="email!=null"> email = #{email} </when> <otherwise> salary = #{salary} </otherwise> </choose> </where> </select>
6.4 set标签
-
set 主要是用于解决修改操作中SQL语句中可能多出逗号的问题,同时补充一个SET关键字
<update id="updateEmployeeByConditionSet"> update employees <set> <if test="lastName!=null and lastName.trim()!=''"> last_name = #{lastName}, </if> <if test="email!=null and email.trim()!=''"> email = #{email}, </if> <if test="salary!=null"> salary = #{salary}, </if> </set> where id = #{id} </update>
6.5 trim标签
-
trim 可以在条件判断完的SQL语句前后添加或者去掉指定的字符
-
prefix: 添加前缀
-
prefixOverrides: 去掉前缀
-
suffix: 添加后缀
-
suffixOverrides: 去掉后缀
-
where 和set 就像是trim的两种特殊情况
-
<select id="getEmployeeByCondition" resultType="com.yongbo.mybatis.entities.Employee"> select id,last_name,email,salary from employees <trim prefix="where" suffixOverrides="and"> <if test="id!=null"> id=#{id} and </if> <if test="lastName!=null&&lastName!="""> last_name=#{lastName} and </if> <if test="email!=null and email.trim()!=''"> email=#{email} and </if> <if test="salary!=null"> salary=#{salary} and </if> </trim> </select>
6.6 foreach标签
-
foreach 主要用于循环迭代
-
collection: 要迭代的集合
-
item: 当前从集合中迭代出的元素
-
open: 开始字符
-
close:结束字符
-
separator: 元素与元素之间的分隔符
-
index:
-
迭代的是List集合: index表示的当前元素的下标
-
迭代的Map集合: index表示的当前元素的key
-
-
EmployeeMapper中的方法
List<Employee> getEmployeesByDeptIds(@Param("ids") List<Integer> ids);
EmployeeMapper.xml文件中的映射
<select id="getEmployeesByDeptIds" resultType="com.yongbo.pojo.Employee"> select id,last_name,email,salary from employee where id in <foreach collection="ids" open="(" close=")" separator="," item="id"> #{id} </foreach> </select>
6.7 sql标签
-
sql 标签是用于抽取可重用的sql片段,将相同的,使用频繁的SQL片段抽取出来,单独定义,方便多次引用.
-
抽取SQL:
<sql id="selectSql"> select id,last_name,email,salary from employees </sql>
-
引用SQL
<include refid="selectSql"></include>
6.8 bind标签和模糊查询
在进行模糊查询时,在映射文件中可以使用concat()函数来连接参数和通配符。另外注意对于特殊字符,比如<,不能直接书写,应该使用字符实体替换。
-
Mapper接口
/** * 根据名字做模糊查询 * @param name 模糊查询的文字 * @return Emp对象List集合 */ List<Emp> findByEname( String name);
-
Mapper映射文件
<!--List<Emp> getByName(String name);--> <select id="findByEname" resultType="employee" > select * from emp where ename like concat('%',#{name},'%') </select>
除此之外,我们也可以使用bind标签,对传入的数据进行拼接
-
Mapper接口
List<Employee> findEmployeeByName(@Param("name") String name);
-
映射文件
<select id="findEmployeeByName" resultType="employee"> <bind name ="namePattern" value="'%'+name+'%"></bind> select * from employee where ename like #{namePattern} </select>
第七章 MyBatis缓存
7.1 缓存机制简介
-
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率
-
MyBatis系统中默认定义了两级缓存
-
一级缓存 使用范围小, 不容易出现数据不一致 默认开启
-
二级缓存 使用范围大, 可能会出现数据不一致 手动开启
-
-
默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
-
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
-
为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
7.2 一级缓存的使用
-
一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。
-
本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
-
在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis的全局配置文件中配置。
设置名 | 描述 | 默认值 |
---|---|---|
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION |
-
一级缓存的工作机制
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中,key: hashCode+查询的SqlId+编写的sql查询语句+参数
7.3 一级缓存失效的几种情况
-
不同的SqlSession对应不同的一级缓存
-
同一个SqlSession但是查询条件不同
-
同一个SqlSession两次查询期间执行了任何一次增删改操作
-
同一个SqlSession两次查询期间手动清空了缓存
-
同一个SqlSession两次查询期间提交了事务
7.4 二级缓存的使用
-
二级缓存(second level cache),全局作用域缓存
-
二级缓存默认不开启,需要手动配置
-
MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
-
二级缓存在 SqlSession 关闭或提交之后才会生效
-
二级缓存使用的步骤:
-
全局配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
-
需要使用二级缓存的映射文件处使用cache配置缓存
<cache />
-
注意:POJO需要实现
Serializable
接口
-
-
二级缓存相关的属性
-
eviction=“FIFO”:缓存回收策略:
-
LRU – 最近最少使用的:移除最长时间不被使用的对象。
-
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
-
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。软引用:软引用需要用SoftReference类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
-
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。弱引用:弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
-
默认的是 LRU。
-
-
flushInterval:刷新间隔,单位毫秒默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size:引用数目,正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly:只读,true/false
-
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
-
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
-
-
7.5 缓存的相关属性设置
-
全局setting的cacheEnabled:
-
配置二级缓存的开关,一级缓存一直是打开的。
-
-
名称空间下设置<cache/>
-
开启当前名称空间下的二级缓存
-
-
select标签的useCache属性:
-
这个属性是用来配置这个select是否使用二级缓存,默认为true。一级缓存一直是使用的。
-
-
所有的增删改查标签中的flushCache属性:
-
增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认 flushCache=false。
-
-
sqlSession.clearCache():只是用来清除一级缓存,清理SQLSession对象中的缓存,和二级缓存无关。
7.6整合第三方缓存
分布式缓存框架:我们系统为了提高系统并发和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache缓存框架。Ehcache:是一种广泛使用的开源java分布式缓存。主要面向通用缓存,javaEE 和 轻量级容器。它具有内存和磁盘存储功能。被用于大型复杂分布式web application.这里的三方缓存是作为二级缓存使用的
使用步骤
-
导入ehcache包,以及整合包,日志包
ehcache-core-2.6.8.jar mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.2.jar
<!-- mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.3</version> </dependency> <!-- slf4j-log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.2</version> <scope>test</scope> </dependency>
-
编写ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="E:\mybatis\ehcache" /> <defaultCache maxElementsInMemory="1" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache> <!-- 属性说明: diskStore:指定数据在磁盘中的存储位置。 defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略 以下属性是必须的: maxElementsInMemory - 在内存中缓存的element的最大数目 maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大 eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断 overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的: timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大 timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区. diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。 diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每隔120s,相应的线程会进行一次EhCache中数据的清理工作 memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) -->
-
配置cache标签
<!--使用第三方二级缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
第八章 MyBatists逆向工程
8.1 逆向工程简介
MyBatis Generator: 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写
-
官方文档地址
MyBatis Generator Core – Introduction to MyBatis Generator
-
官方工程地址
Releases · mybatis/generator · GitHub
8.2 逆向工程的配置
-
导入逆向工程依赖
<!-- mybatis-generator-core --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.6</version> </dependency>
-
编写MBG的配置文件mbg.xml(重要几处配置),参考官方文档,a) 注意:如果使用的是MySQL8,在jdbcConnection标签中还需要添加以下标签
<property name="nullCatalogMeansCurrent" value="true" />
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- id属性:设置一个唯一标识 targetRuntime属性值说明: MyBatis3Simple:基本的增删改查 MyBatis3:带条件查询的增删改查 --> <context id="simple" targetRuntime="MyBatis3"> <!--设置连接数据库的相关信息--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root"/> <!--设置JavaBean的生成策略--> <javaModelGenerator targetPackage="com.yongbo.mbg.pojo" targetProject="src/main/java"/> <!--设置SQL映射文件的生成策略--> <sqlMapGenerator targetPackage="com.yongbo.mbg.mapper" targetProject="src/main/resources"/> <!--设置Mapper接口的生成策略--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.yongbo.mbg.mapper" targetProject="src/main/java"/> <!--逆向分析的表--> <table tableName="employee" domainObjectName="Employee"/> <table tableName="department" domainObjectName="Department"/> </context> </generatorConfiguration>
-
参考官方文档创建代码生成器运行代码
@Test public void testMGB() throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException { List<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("src/main/resources/mbg.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); }
8.3 逆向工程的使用
-
基本查询的测试
/* 测试获取一个对象 */ @Test public void testSelectByPrimaryKey() throws IOException { //设置MyBatis全局配置文件的路径 String resource = "mybatis-config.xml"; //读取类路径下的配置文件得到输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSession对象,相当于Connection对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //获取Mapper代理对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.selectByPrimaryKey(1); System.out.println(employee); } finally { //关闭sqlSession sqlSession.close(); } }
-
带条件查询的测试
//查询名字里包含a并且工资大于等于10000的员工 @Test public void testSelectByExample() throws IOException { //设置MyBatis全局配置文件的路径 String resource = "mybatis-config.xml"; //读取类路径下的配置文件得到输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSession对象,相当于Connection对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //获取Mapper代理对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); //创建EmployeeExample对象 EmployeeExample employeeExample = new EmployeeExample(); //创建Criteria对象 EmployeeExample.Criteria criteria = employeeExample.createCriteria(); //查询名字里包含a并且工资大于等于10000的员工 criteria.andLastNameLike("%a%").andSalaryGreaterThanOrEqualTo(10000.00); List<Employee> employees = mapper.selectByExample(employeeExample); for (Employee employee : employees) { System.out.println(employee); } } finally { //关闭sqlSession sqlSession.close(); } } //测试名字了包含a或者工资小于等于10000的员工 @Test public void testSelectByExample() throws IOException { //设置MyBatis全局配置文件的路径 String resource = "mybatis-config.xml"; //读取类路径下的配置文件得到输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSession对象,相当于Connection对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //获取Mapper代理对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); //创建EmployeeExample对象 EmployeeExample employeeExample = new EmployeeExample(); //创建Criteria对象 EmployeeExample.Criteria criteria = employeeExample.createCriteria(); //查询名字里包含a的员工 criteria.andLastNameLike("%a%"); EmployeeExample.Criteria criteria1 = employeeExample.createCriteria(); //查询工资小于等于10000的员工 criteria1.andSalaryLessThanOrEqualTo(10000.00); //使用or关联两个Criteria对象 employeeExample.or(criteria1); List<Employee> employees = mapper.selectByExample(employeeExample); for (Employee employee : employees) { System.out.println(employee); } } finally { //关闭sqlSession sqlSession.close(); } }
第九章 PageHelper插件
9.1 PageHelper 分页插件简介
-
PageHelper是MyBatis中非常方便的第三方分页插件
-
官方文档:
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
-
我们可以对照官方文档的说明,快速的使用插件
9.2 PageHelper的使用步骤
-
导入依赖
<!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0.0</version> </dependency>
-
在MyBatis全局配置文件中配置分页插件
<plugins> <!--配置PageHelper分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
-
使用PageHelper提供的方法进行分页
-
可以使用更强大的PageInfo封装返回结果
9.3 Page对象的使用
在查询之前通过PageHelper.startPage(页码,条数)设置分页信息,该方法返回Page对象
@Test public void testPageHelper() throws IOException { //设置MyBatis全局配置文件的路径 String resource = "mybatis-config.xml"; //读取类路径下的配置文件得到输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSession对象,相当于Connection对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //获取Mapper代理对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); //设置分页信息,每页显示3条记录,获取第1页 Page<Object> page = PageHelper.startPage(1, 3); //获取所有员工 List<Employee> employees = mapper.getEmployees(); System.out.println("当前页是:"+page.getPageNum()); System.out.println("每页显示的条数是:"+page.getPageSize()); System.out.println("总页数是:"+page.getPages()); System.out.println("总记录数是:"+page.getTotal()); System.out.println("当前页中的记录有:"); for (Employee employee : employees) { System.out.println(employee); } } finally { //关闭sqlSession sqlSession.close(); } }
9.4 PageInfo对象的使用
在查询完数据后,使用PageInfo对象封装查询结果,可以获取更详细的分页信息以及可以完成分页逻辑
@Test public void testPageHelper() throws IOException { //设置MyBatis全局配置文件的路径 String resource = "mybatis-config.xml"; //读取类路径下的配置文件得到输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSession对象,相当于Connection对象 SqlSession sqlSession = sqlSessionFactory.openSession(); try { //获取Mapper代理对象 EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); //设置分页信息 Page<Object> page = PageHelper.startPage(3, 8); //获取所有员工 List<Employee> employees = mapper.getEmployees(); //创建PageInfo对象设置每页只显示5个页码 PageInfo<Employee> pageInfo = new PageInfo<>(employees, 5); System.out.println("当前页是:"+pageInfo.getPageNum()); System.out.println("每页显示的条数是:"+pageInfo.getPageSize()); System.out.println("总页数是:"+pageInfo.getPages()); System.out.println("总记录数是:"+pageInfo.getTotal()); System.out.println("是否有上一页:"+pageInfo.isHasPreviousPage()); System.out.println("上一页是:"+pageInfo.getPrePage()); System.out.println("是否有下一页:"+pageInfo.isHasNextPage()); System.out.println("下一页是:"+pageInfo.getNextPage()); System.out.println("是否是第一页:"+pageInfo.isIsFirstPage()); System.out.println("是否是最后一页:"+pageInfo.isIsLastPage()); System.out.println("导航页的第一个页码是:"+pageInfo.getNavigateFirstPage()); System.out.println("导航页的最后一个页码是:"+pageInfo.getNavigateLastPage()); System.out.println("导航页的总页码是:"+pageInfo.getNavigatePages()); System.out.println("当前页中的记录有:"); for (Employee employee : employees) { System.out.println(employee); } System.out.println("页码信息:"); int[] navigatepageNums = pageInfo.getNavigatepageNums(); for (int navigatepageNum : navigatepageNums) { System.out.print(navigatepageNum+" "); } } finally { //关闭sqlSession sqlSession.close(); } }