黑马程序员技术交流社区
标题:
【广州校区】MyBatis应用分析与最佳实践
[打印本页]
作者:
Mylo
时间:
2019-7-11 16:32
标题:
【广州校区】MyBatis应用分析与最佳实践
本帖最后由 Mylo 于 2019-7-11 16:33 编辑
MyBatis应用分析与最佳实践
A. 传统 JDBC 问题的解决
使用 DataSource 解决资源管理问题
DBUtils 提供了各种 ResultSetHandler 帮我们处理结果集
使用 QueryRunner 帮我们实现简化
不用我们不断地调用 rs.getXxx 然后 obj.setXxx
不需要写释放资源代码
SpringJDBC
实现 RowMapper 接口,重写 mapRow 方法
转换结果集返回 Object
==JdbcTemplate 和 DBUtils 很像==
但 JdbcTemplate 可能需要自己写 RowMapper
我们可以创建一个通用的 BaseRowMapper<T>
通过反射实现:
名称对应(适配驼峰名命和下划线)
适配字段类型转换
jdbcTemplate.query(sql, new BaseRowMapper(Employee. class));
DBUtils 和 SpringJDBC还没解决的问题
SQL 语句硬编码
参数只能按顺序传入(占位符)
没有实现实体类到数据库的映射
没有提供缓存功能
B. ORM
Hibernate 是一个 ORM 框架
它解决了创建连接,释放资源的重复代码问题
解决了 ResultSet 的映射代码问题
提供了查询缓存
解决了写 SQL 的问题
Hibernate 缺点
每次操作都是表的所有字段,无法做到制定部分字段
自动生成的 SQL 很难进行优化
不支持动态 SQL
MyBatis
解决了 ResultSet 问题
解决了 SQL 硬编码问题
MyBatis 特性
使用连接池对连接进行管理
SQL 和代码分离,集中管理
参数映射和动态 SQL
结果集映射
缓存管理
重复 SQL 提取
插件机制
核心对象
SqlSessionFactoryBuilder
只要产生了 SqlSessionFactory 就可以销毁
SqlSessionFactory(单例)
在整个项目中要一直存在
全局声明周期
SqlSession
一次会话
Mapper
一次请求
C. MyBatis 配置文件(mybatis-config.xml)
根标签 <configuration>
对应 MyBatis 的 Configuration 类
<properties>
读取配置文件
<settings>
对 MyBatis 一些核心行为做配置
对日志输出的控制
全局缓存
延迟加载
<typeAliases>
设置别名
==<typeHandlers>==
Java 类型到 数据库字段类型转换
==<objectFactory>==
把数据库中的字段映射成 java 对象
每次从结果集里获取数据返回对象,默认会调用 DefaultObjectFactory 的 create 方法
<plugins>
插件可以拦截 MyBatis 四大对象
Ececutor
ParameterHandler
ResultHandler
StatementHandler
<environments>
配置数据库环境
<mappers>
定位 mapper 映射文件的位置
<?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"/>
<settings>
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 控制全局缓存(二级缓存)-->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
<setting name="proxyFactory" value="CGLIB" />
<!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
<setting name="localCacheScope" value="STATEMENT"/>
<setting name="localCacheScope" value="SESSION"/>
</settings>
<typeAliases>
<typeAlias alias="blog" type="com.gupaoedu.domain.Blog"/>
</typeAliases>
<typeHandlers>
<typeHandler handler="com.gupaoedu.type.MyTypeHandler"></typeHandler>
</typeHandlers>
<!-- 对象工厂 -->
<objectFactory type="com.gupaoedu.objectfactory.GPObjectFactory">
<property name="gupao" value="666"/>
</objectFactory>
<plugins>
<plugin interceptor="com.gupaoedu.interceptor.SQLInterceptor">
<property name="gupao" value="betterme"/>
</plugin>
<plugin interceptor="com.gupaoedu.interceptor.MyPageInterceptor">
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<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>
<mappers>
<mapper resource="BlogMapper.xml"/>
<mapper resource="BlogMapperExt.xml"/>
</mappers>
</configuration>
自定义类型转换
public class MyTypeHandler extends BaseTypeHandler<String> {
public vo id setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
"设置 String 类型的参数的时候调用,Java 类型到 JDBC 类型"
System. out .println(" " --------------- setNonNullParameter1 :" "+parameter);
ps.setString(i, parameter);
}
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
"根据列名获取 String 类型的参数的时候调用,JDBC 类型到 java 类型"
System. out .println(" " --------------- getNullableResult1 :" "+columnName);
return rs.getString(columnName);
}
"后面2个方法不用"
}
把自定义类型转换注册到核心配置中
<typeHandlers>
<typeHandler handler ="com.gupaoedu.type.MyTypeHandler"></ typeHandler>
</typeHandlers>
使用
<insert id="insertBlog" parameterType="com.gupaoedu.domain.Blog">
insert into blog (bid, name, author_id)
values (#{bid,jdbcType=INTEGER},
#{name,jdbcType=VARCHAR,typeHandler=com.gupaoedu.type.MyTypeHandler},
#{authorId,jdbcType=INTEGER})
</insert>
返回值的时候,从 JDBC 类型到 Java 类型,在 resultMap 的列上指定 typehandler
<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.gupaoedu.type.MyTypeHandler"/>
D. 动态 SQL 配置
<if>
<choose>(<when>,<otherwise>)
<trim>(<where>,<set>)
<foreach><select id ="selectDept" parameterType ="int" resultType ="com.gupaoedu.crud.bean.Department">
select * from tbl_dept where 1 = 1
<if test="deptId != null">
and dept_id = #{deptId, jdbcType=INTEGER}
</if>
</select>
只需要一个条件
<select id="getEmpList_choose" resultMap="empResultMap" parameterType="com.gupaoedu.crud.bean.Employee"> SELECT * FROM tbl_emp e <where> <choose> <when test="empId !=null"> e.emp_id = #{emp_id, jdbcType=INTEGER} </when> <when test="empName != null and empName != ''"> AND e.emp_name LIKE CONCAT(CONCAT('%', #{emp_name, jdbcType=VARCHAR}),'%') </when> <when test="email != null "> AND e.email = #{email, jdbcType=VARCHAR} </when> <otherwise> </otherwise> </choose> </where></select>
按条件插入时使用
需要去掉 where、and、逗号之类的符号的时候
注意最后一个条件 dId 多了一个逗号,就是用 trim 去掉的
<update id ="updateB yPrimaryKeySelective" parameterType ="com.gupaoedu.crud.bean.Employee"> update tbl_emp <set> <if test ="empName != null"> emp_name = #{empName,jdbcType=VARCHAR}, </if> <if test ="gender != null"> gender = #{gender,jdbcType=CHAR}, </if> <if test ="email != null"> email = #{email,jdbcType=VARCHAR}, </if> <if test ="dId != null"> d_id = #{dId,jdbcType=INTEGER}, </if> </set> where emp_id = #{empId,jdbcType=INTEGER}</update>
trim 用来指定或者去掉前缀或者后缀
<insert id ="insertSelective" parameterType="com.gupaoedu.crud.bean.Employee"> insert into tbl_emp <trim prefix ="("suffix=")" suffixOverrides=","> <if test="empId != null"> emp_id, </if> <if test ="empName != null"> emp_name, </if> <if test ="dId != null"> d_id, </if> </trim> <trim prefix="values("suffix=")" suffixOverrides=","> <if test="empId != null"> #{empId,jdbcType=INTEGER}, </if> <if test ="empName != null"> #{empName,jdbcType=VARCHAR}, </if> <if test ="dId != null"> #{dId,jdbcType=INTEGER}, </if> </trim></insert>
E. 批量操作
批量插入 | 批量更新 | 批量删除
如果在 java 代码里写 for 循环批量插入,会不断的创建连接,耗性能
我们使用动态 SQL 标签实现
<foreach> 动态生成 SQL 语句
如果数据量特别大,生成的 SQL 就会特别大
==数据库有接收数据包大小限制==
max_allowed_packet=4M
<!-- 批量插入 --><insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true"> <selectKey resultType="long" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into tbl_emp(emp_id, emp_name, gender,email, d_id) values <foreach collection="list" item="emps" index="index" separator=","> (#{emps.empId},#{emps.empName},#{emps.gender},#{emps.email},#{emps.dId}) </foreach></insert>
Batch Executor
定义一个批量操作的执行器
在 <settings> 标签里设置
<defalutExecutorType>
SIMPLE(默认)
REUSE
BATCH(批处理)
也可以在创建会话的时候指定
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
F. 嵌套(关联)查询 / N + 1 / 延时加载
对象关联时有 2 种方式
嵌套结果
<select id="selectBlogWithAuthorResult" resultMap="BlogWithAuthorResultMap"> select b.bid, b.name, b.author_id, a.author_id , a.author_name from blog b left join author a on b.author_id=a.author_id where b.bid = #{bid, jdbcType=INTEGER}</select> <resultMap id="BlogWithAuthorResultMap" type="com.gupaoedu.domain.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <!-- 联合查询,将author的属性映射到ResultMap --> <association property="author" javaType="com.gupaoedu.domain.Author"> <id column="author_id" property="authorId"/> <result column="author_name" property="authorName"/> </association></resultMap>
嵌套查询
当调用 getAuthor 的时候就会触发 selectAuthor 查询
这样子会出现 N + 1 的问题
1 是查询当前对象
N 是该对象关联的查询
==使用延迟加载解决==
在全局配置中配置延迟加载(默认是关闭的)
<select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap"> select b.bid, b.name, b.author_id, a.author_id , a.author_name from blog b left join author a on b.author_id=a.author_id where b.bid = #{bid, jdbcType=INTEGER}</select><!-- 另一种联合查询(一对一)的实现,但是这种方式有“N+1”的问题 --><resultMap id="BlogWithAuthorQueryMap" type="com.gupaoedu.domain.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <association property="author" javaType="com.gupaoedu.domain.Author" column="author_id" select="selectAuthor"/></resultMap><select id="selectAuthor" parameterType="int" resultType="com.gupaoedu.domain.Author"> select author_id authorId, author_name authorName from author where author_id = #{authorId}</select><!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false --><setting name="lazyLoadingEnabled" value="true"/><!--当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过 select 标签的 fetchType 来覆盖--><setting name="aggressiveLazyLoading" value="false"/><!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认 JAVASSIST --><setting name="proxyFactory" value="CGLIB"/>
G. 翻页
逻辑翻页与物理翻页的区别
逻辑翻页:假的翻页,把所有的数据都加载到内存,然后把需要返回
物理翻页:真正的翻页
逻辑翻页的支持:RowBounds
数据量大的时候很影响性能
物理翻页的几种实现方式
直接在 Mapper 的 SQL 代码里写 limit,然后传入参数
使用 PageHelper
帮我们自动生成分页代码
使用的是 MyBatis 插件
H. 配置太多
MyBatis 只是帮我们管理了数据源,创建连接,释放资源,动态 SQL
但是还是需要写很多 SQL,包括接口映射等
==使用逆向工程可以解决==
MyBatis 官方提供了这个工程 mybatis-generator,运行 main 方法就会生成文件
I. 通用 Mapper
==主要解决单表的增删改查问题,并不适用于多表关联查询的场景==
MyBatis 支持继承,Mapper 只能继承 SQL 语句的标签
如果字段发生变化了
使用继承
自己的类继承动态生成的类
这样就不用改自己的 mapper.xml
==但是每一张表都多了 2 个 Mapper 类和 2 个映射文件==
==使用通用 Mapper==
继承一个通用的类,自动帮你写好通用的逻辑
J. MyBatis-Plus
通用 crud
条件构造
代码生成
翻页
批处理
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2