这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
简介
-
来源
MyBatis 本是 apache 的一个开源项目 iBatis
2010 年 由 apache software foundation 迁移到了 google code,并且改名为 MyBatis
2013 年 11 月迁移到 Github
-
作用
MyBatis 是一款优秀的持久层框架
它支持自定义 SQL、存储过程以及高级映射
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
-
地址
GitHub:github.com/mybatis
Maven:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency>
持久层
-
数据持久化是指数据由瞬时状态转化成持久状态的过程
- 瞬时状态:内存中存储的内容,断电后就会丢失
- 持久状态:数据库(JDBC)、文件(IO)
-
持久层是完成持久化工作的代码
传统 JDBC
-
建立测试数据库
CREATE DATABASE `mybatis`; CREATE TABLE `user` ( `id` int NOT NULL, `name` varchar(30) DEFAULT NULL, `pwd` varchar(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -
一般步骤
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.Scanner; public class JDBCDemo { public static void main(String[] args) throws Exception { // 注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 获取连接对象 String url = "jdbc:mysql://localhost:3306/mybaitis"; Connection conn = DriverManager.getConnection(url, "root", "root"); // SQL 语句 String sql = "select * from users where username = ? and password = ? "; // 获取执行语句对象 PreparedStatement pst = conn.prepareStatement(sql); // 设置问号占位符参数 pst.setObject(1, "小 a"); pst.setObject(2, "123456"); // 调用执行者对象方法,执行SQL语句获取结果集 ResultSet rs = pst.executeQuery(); // 处理结果集 while (rs.next()) { System.out.println(rs.getString("username") + " : " + rs.getString("password")); } // 关闭资源 rs.close(); pst.close(); conn.close(); } }
使用 MyBatis
-
导入 jar 包
maven 构建的项目在 pom.xml 中添加
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> -
配置文件 mybatis-config-xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="12345"/> </dataSource> </environment> </environments> </configuration> -
编写工具类
-
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的
-
SqlSessionFactoryBuilder 通过 XML 配置文件可以构建出 SqlSessionFactory 实例
-
SqlSession 包含向数据库执行 sql 的所有方法,通过 SqlSessionFactory 获取
-
utils 包新增工具类
package 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; public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { // 读取配置文件,获取 SqlSessionFactory 实例 try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * 获取 sqlSession 实例 * * @return SqlSession 实例 */ public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
-
-
编写代码并测试
-
pojo 包中增加 User 类
package pojo; public class User { int id; String name; String pwd; // setter/getter 省略... } -
dao 包中增加 UserDao 接口
package dao; import pojo.User; import java.util.List; public interface UserDao { List<User> listUsers(); } -
dao 包中增加 UserMapper.xml
指定命名空间,select 查询的 id 对应 UserDao 方法名
<?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="dao.UserDao"> <select id="listUsers" resultType="pojo.User"> select * from user </select> </mapper> -
mybatis 配置文件中在 configuration 标签下添加 mapper 位置信息
<mappers> <mapper resource="dao/UserMapper.xml"></mapper> </mappers> -
maven pom 文件中要配置开启包下的 xml 文件资源过滤
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>true</filtering> </resource> </resources> </build> -
junit 测试
public class UserDaoTest { @Test public void test() { // 工具类获取 SqlSession SqlSession sqlSession = MyBatisUtil.getSqlSession(); // sqlSession 获取 userDao 实现实例 UserDao userDao = sqlSession.getMapper(UserDao.class); // 执行 SQL 方法 List<User> users = userDao.listUsers(); System.out.println(users); } }
-
配置文件
-
环境配置
MyBatis 可以配置成适应多种环境,但每个 SqlSessionFactory 实例只能选择一种环境
配置多个 environment,使用 default 属性进行指定
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <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> -
properties
属性可以在外部进行配置,并可以进行动态替换,可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置
db.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username=root password=12345在 mybatis-config 中添加
<properties resource="db.properties"> <property name="username" value="root"/> <property name="password" value="12345" /> </properties>resource 指定的配置文件中的内容优先级高于标签内的配置
-
类型别名
配置 typeAliases
<typeAliases> <typeAlias type="pojo.User" alias="User"></typeAlias> <package name="pojo"></package> </typeAliases>pojo 上注解
@Alias("User") public class User { }类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
包别名配置会在包名下面搜索需要的 Java Bean,在没有注解时,会使用 Bean 的首字母小写的非限定类名来作为它的别名
若有注解,则别名为其注解值
-
settings
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="logImpl" value="LOG4J"/> <settings>cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找
-
映射器
指定资源路径
<mappers> <mapper resource="dao/UserMapper.xml"></mapper> </mappers>指定类名,需要 mapper.xml 和 mapper 接口同名且在同一个包下
<mappers> <mapper class="dao.UserMapper"></mapper> </mappers>指定包名,需要 mapper.xml 和 mapper 接口同名且在同一个包下
<mappers> <package name="dao"></package> </mappers>
XML 映射器
-
查询
dao 包中 mapper 接口
public interface UserDao { User getUserById(int id); }UserMapper.xml
<mapper namespace="dao.UserDao"> <select id="getUserById" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select> </mapper> -
增删改
dao 包中 mapper 接口
public interface UserDao { int addUser(User user); int updateUser(User user); int deleteUserById(int id); }UserMapper.xml
<insert id="addUser" parameterType="pojo.User"> insert into user values (#{id}, #{name}, #{pwd}) </insert> <update id="updateUser" parameterType="pojo.User"> update user set name = #{name}, pwd = #{pwd} where id = #{id} </update> <delete id="deleteUserById" parameterType="int"> delete from user where id = #{id} </delete>增删改执行方法后,需要提交事务
sqlSession.commit(); // 可以使用 sqlSessionFactory.openSession(true) 开启自动提交 -
常用属性
- id:在命名空间中唯一的标识符,可以被用来引用这条语句,与 namespace 接口方法名一致
- parameterType:将会传入这条语句的参数的类全限定名或别名
- 传入普通对象,使用 #{属性名} 获取属性
- 传入 map,使用 #{key} 获取 value
- 传入一个基本类型,直接使用 #{参数名} 获取
- resultType:期望从这条语句中返回结果的类全限定名或别名,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型
-
resultMap 结果集映射
-
简单映射
当查询出的结果集无法与 Bean 字段映射时,需要指定 resultMap
例如,数据库中字段为 password,Bean 中为 pwd,能成功映射的字段不必要配置
<resultMap type="pojo.User" id="UserMap"> <result column="id" property="id"></result> <result column="name" property="name"></result> <result column="password" property="pwd"></result> </resultMap> <select id="listUsers" resultMap="UserMap"> select * from user </select> -
复杂映射
例如学生和老师的多对一关系
public class Student { int id; String name; Teacher teacher; // ... } public class Teacher { int id; String name; // ... }StudentMapper.xml 中配置 resultMap,association 标签 select 属性指向一个查询,column 作为查询参数,javaType
<resultMap id="StudentMap" type="pojo.Student"> <result column="id" property="id"></result> <result column="name" property="name"></result> <association property="teacher" column="tid" javaType="pojo.Teacher" select="getTeacher" ></association> </resultMap> <select id="listStudents" resultMap="StudentMap"> SELECT * FROM student </select> <select id="getTeacher" parameterType="int" resultType="pojo.Teacher"> SELECT * FROM student where id = #{id} </select>也可以用一个查询完成,需要在 SQL 中对结果集定义别名
<resultMap id="StudentMap" type="pojo.Student"> <result column="sid" property="id"></result> <result column="sname" property="name"></result> <association property="teacher" column="tid" javaType="pojo.Teacher"> <result column="tid" property="id"></result> <result column="tname" property="name"></result> </association> </resultMap> <select id="listStudents" resultMap="StudentMap"> SELECT s.id sid, s.name sname, t.id tid, t.name tname FROM student s, teacher t where s.tid = t.id </select>同样的,老师和学生的一对多关系,可如下解决
// pojo public class Teacher { int id; String name; List<Student> students; } public class Student { int id; String name; int tid; }<!-- TeacherMapper.xml --> <resultMap id="TeacherMap" type="pojo.Teacher"> <result column="tid" property="id"></result> <result column="tname" property="name"></result> <collection property="students" ofType="pojo.Student"> <result column="sid" property="id"></result> <result column="stid" property="tid"></result> <result column="sname" property="name"></result> </collection> </resultMap> <select id="getTeacher" resultMap="TeacherMap"> select t.id tid, t.name tname, s.id sid, s.name sname, s.tid stid from teacher t, student s where t.id = s.tid and t.id = #{id} </select>association 用来处理多对一,collection 用来处理一对多,javaType 表示属性的类型,ofType 表示集合中的元素类型
-
生命周期
-
生命周期和作用域使用有误可能会导致严重的并发问题
-
基本流程
SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession -> getMapper() -
SqlSessionFactoryBuilder
- 通过配置文件创建 SqlSessionFactory,创建完毕后,就不在需要它了
- 最佳的作用域是方法作用域(也就是局部变量)
-
SqlSessionFactory
- 类似于连接池
- 一旦创建就在运行期间一直存在
- 最佳作用域为应用作用域
- 最简单的就是使用单例模式或者静态单例模式
-
SqlSession
- 表示连接到数据库的一个请求,使用完毕后需要调用 close() 释放资源
- SqlSession 的实例不是线程安全的,所以它的最佳的作用域是请求或方法作用域
日志
-
日志工厂
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
SLF4J | Apache Commons Logging | Log4j 2 | Log4j | JDK logging在配置文件的 setting 标签中可以设置具体使用哪一种日志实现
<settings> <setting name="logImpl" value="STDOUT_LOGGING"></setting> </settings>STDOUT_LOGGING 是标准日志实现,可以直接使用
若使用 LOG4J 需要导入相关包并指定 logImpl 为 LOG4J
-
LOG4J
LOG4J 是 Apache 的开源项目,用于实现输入日志到控制台、GUI 组件或文件,可用外部文件灵活配置
pom.xml
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>log4j.properties
#将等级为 DEBUG 的信息输出到控制台和文件 log4j.rootLogger=DEBUG,console,file #控制台输出设置 log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.Target=System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%5p [%d{yyyy-MM-dd HH:mm:ss}][%t][%F:%L] - %m%n #文件输出设置 log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/log4j.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%5p [%d{yyyy-MM-dd HH:mm:ss}][%t][%c] - %m%n #输出级别设置 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG配置完 pm.xml、mybatis-config.xml、log4j.properties 运行代码,可见 LOG4J 打印信息在控制台
也可以在类中主动使用 Logger 类自定义打印内容
public class LoggerTest { // 获取 logger 实例 static Logger logger = Logger.getLogger(LoggerTest.class); @Test public void log4jTest() { // 测试不同的日志级别 logger.info("info => info 内容"); logger.debug("debug => debug 内容"); logger.warn("warn => warn 内容"); logger.error("error => error 内容"); } }
分页
-
当数据量过大时,需要分页以减少数据处理量,并可以多页显示,提升用户体验
-
mysql limit 关键字分页
SELECT * FROM user limit startIndex, pageSize SELECT * FROM user limit pageSize // 相当于 SELECT * FROM user limit 0, pageSizemapper.xml
<select id="listUsersLimit" parameterType="map" resultType="pojo.User"> select * from user limit #{startIndex},#{pageSize} </select>dao 或 mapper 接口
public interface UserDao { List<User> listUsersLimit(Map map); } -
RowBounds 分页
mapper.xml 中的 SQL 查询中不做数据量的限制,通过 sqlSession 的查询方法和 RowBounds 对象进行数量的限制
List<User> users = sqlSession.selectList("dao.UserDao.listUsersByRowBounds",null, new RowBounds(1,2));
注解
-
CURD 注解
不需要在 mapper.xml 中指定 SQL,直接在注解中传入 SQL 语句即可,适合简单的查询
可以使用 @Select/@Update/@Delete/@Insert
public interface UserDao { @Select("select * from user") List<User> listUserByAnnotation(); } -
参数注解
多个基本类型或 String 类型参数需要通过 @Param 指定名称,以便在 SQL 语句中使用
不指定也可以在 SQL 中使用 arg0,arg1 或 param1,param2 等按顺序来获取参数
public interface UserDao { @Select("select * from user where name = #{name} and pwd = #{pwd}") User getUserByNameAndPwd(@Param("name") String name, @Param("pwd") String pwd); }
动态 SQL
-
if
方法传入 id 为 0 时不开启 if 内 and 条件,则查询全部。传入 id 不为 0 时,则查询指定 id
<select id="listStudentsById" parameterType="int" resultType="pojo.Student"> SELECT * FROM student WHERE <if test="id != 0"> id = #{id} </if> </select> -
choose,when,otherwise
类似于 switch 条件控制
<select id="listStudentsById" parameterType="int" resultType="pojo.Student"> SELECT * FROM student <where> <choose> <when test="id == 1"> id = 2 </when> <when test="id == 2"> and id = 1 </when> <otherwise> and id = 3 </otherwise> </choose> </where> </select> -
where,trim,set
在上面 if 的例子中,如果 if 条件不满足,不会拼接 if 中的内容,只有一个 where 会导致 SQL 错误
可以通过 where 标签更优雅的解决,
<select id="listStudentsById" parameterType="int" resultType="pojo.Student"> SELECT * FROM student <where> <if test="id != 0"> id = #{id} </if> ... </whrer> </select>where 标签当内部有内容返回时才会返回 where 条件语句,并且自动去除子句开头的 and/or
trim 和 where 功能类似,可以指定拼接子句时要替换的关键字,prefix 表示最后返回的子句,prefixOverrides/suffixOverrides 表示需要被删除或添加的前缀或后缀
<trim prefix="where" prefixOverrides="and |or "> <choose> <when test="id == 1"> and id = 2 </when> <when test="id == 2"> and id = 1 </when> <otherwise> and id = 3 </otherwise> </choose> </trim>set 用于实现动态的更新语句
<update id="updateStudent" parameterType="map"> update student <set> <if test="id != null">id = #{id},</if> <if test="name != null">name = #{name},</if> <if test="tid != null">tid = #{tid}</if> </set> </update>trim 实现 set 功能
<trim prefix="SET" suffixOverrides=","> ... </trim> -
foreach
select * from student where id in (1, 2, 3)类似于上面的 SQL,当 in 子句的范围不确定时,可以使用 foreach
<select id="listStudents" parameterType="list" resultType="pojo.Student"> SELECT * FROM student <where> <foreach item="item" index="index" collection="list" open="id in (" separator="," close=")"> #{item} </foreach> </where> </select>如果 list 元素个数为 0,则不会返回任何字符串
-
sql 片段
sql 标签定义的片段可以通过 include 引入,提高复用性
<sql id="sql-if-id"> <if test="id != 0"> id = #{id} </if> </sql>在其他标签中使用
<select id="listStudentsById" parameterType="int" resultType="pojo.Student"> SELECT * FROM student <where> <include refid="sql-if-id"></include> </whrer> </select>
缓存
-
缓存是内存中的临时数据,可以提升查询效率、减少数据库压力,适合经常查询但不经常改变的数据
-
MyBatis 默认开启一级缓存,是 SqlSession 级别的缓存
从打开一个 SqlSession 实例开始,重复执行两次查询方法,只会执行一次 SQL,返回的数据指向同一对象
如何两次查询之间执行了增删改操作或 sqlSession 调用了 clearCache 方法,则第二次会重新查询
-
MyBatis 二级缓存可以手动开启,是 namespace 级别的缓存
先要在 myatis-config.xml 中配置开启缓存
<settings> <setting name="cacheEnabled" value="true"></setting> </settings>在要使用二级缓存的 mapper.xml 通过 cache 标签中开启
<cache /> <!--一些属性--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>开启了二级缓存后,在同一个 mapper 中有效
一个 SqlSession 实例关闭后,其中的一级缓存会转存到二级缓存中,可供另一个 Sqlsession 实例使用