文章目录
MyBatis
MyBatis 是一款优秀的 持久层框架 ,用于简化 JDBC 开发
持久层:
负责将数据保存到数据库的那一层代码。
MyBatis 和Hibernate
Hibernate 和 MyBatis 都是目前业界中主流的==对象关系映射(ORM)==框架。
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中.
sql 优化方面
- Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库。不需要编写大量的 SQL,就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化。提供了日志、缓存、级联(级联比 MyBatis 强大)等特性。
- MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对较大。
开发方面
- MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。
- Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。
Hibernate 优势
- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
Mybatis优势
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- MyBatis 容易掌握,而 Hibernate 门槛较高。
应用场景
MyBatis 适合需求多变的互联网项目,例如电商项目、金融类型、旅游类、售票类项目等。
Hibernate 适合需求明确、业务固定的项目,例如 OA 项目、ERP 项目和 CRM 项目等
总结
总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
工作流程
- 通过Reader对象读取Mybatis映射文件
- 通过SqlSessionFactoryBuilder对象创建SqlSessionFactory对象
- 每个数据库对应一个 SqlSessionFactory 实例
- 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
- 获取当前线程的SQLSession
- SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
- 事务默认开启
- 通过SQLSession读取映射文件中的操作编号,从而读取SQL语句
- 提交事务
- 关闭资源
环境配置
-
属性 properties
-
设置 setting
- lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
fetchType
属性来覆盖该项的开关状态。
- lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
-
类型别名(typeAliases)
- 类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余
-
类型处理器(typeHandlers)
- 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型
<?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"/>
<!-- 设置一个默认的连接环境信息 -->
<environments default="mysql_developer">
<!-- 连接环境信息,取一个任意唯一的名字 -->
<environment id="mysql_developer">
<!-- mybatis使用jdbc事务管理方式 -->
<transactionManager type="jdbc"/>
<!-- mybatis使用连接池方式来获取连接 -->
<dataSource type="pooled">
<!-- 配置与数据库交互的4个必要属性 -->
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</configuration>
实体与映射关系
映射器(mappers)
实体与表的映射
<?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 namespace="cn.javaee.mybatis.Student">
<!-- resultMap标签:映射实体与表
type属性:表示实体全路径名
id属性:为实体与表的映射取一个任意的唯一的名字
-->
<resultMap type="student" id="studentMap">
<!-- id标签:映射主键属性
result标签:映射非主键属性
property属性:实体的属性名
column属性:表的字段名
-->
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sal" column="sal"/>
</resultMap>
</mapper>
配置文件的映射
<mappers>
<mapper resource="StudentMapper.xml"/>
</mappers>
编写SQL
cache
– 给定命名空间的缓存配置。cache-ref
– 其他命名空间缓存配置的引用。resultMap
– 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。sql
– 可被其他语句引用的可重用语句块。insert
– 映射插入语句update
– 映射更新语句delete
– 映射删除语句select
– 映射查询语句
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
这个语句被称作 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
#{id} 告诉 MyBatis 创建一个预处理语句参数,通过 JDBC,这样的一个参数在 SQL 中会由一个 "?" 来标识,并被传递到一个新的预处理语句中
动态 SQL
根据表达式的值动态拼接sql,以此来完成动态sql的功能。
多条件查询 会自动组合成一个正常的where语句
- if
<select id="select" resultType="Blog">
<!--
FROM子句只出现一次
WHERE字句可以添加多个条件,但WHERE关键字只出现一次
-->
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="name!= null">
AND name like #{title}
</if>
</select>
- where
如果where后面没有条件,然后需要直接写if判断
<select id="select"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="title != null">
AND title like #{title}
</if>
<if test="name!= null">
AND name like #{title}
</if>
<where>
</select>
- choose (when, otherwise)
相当于 java 里面的 switch 语句
- trim
prefix:前缀prefixoverride:去掉第一个and或者是or
select * from test
<trim prefix="WHERE" prefixoverride="AND丨OR">
<if test="a!=null and a!=' '">AND a=#{a}<if>
<if test="b!=null and b!=' '">AND a=#{a}<if>
</trim>
-
set
-
foreach
foreach主要用在构建in条件中
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
调用映射文件的SQL代码片段
<insert id="add" parameterType="Student">
INSERT INTO ZHONGFUCHENG.STUDENTS (ID, NAME, SAL) VALUES (#{id},#{name},#{sal});
</insert>
public class StudentDao {
public void add(Student student) throws Exception {
//得到连接对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
try{
//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
sqlSession.insert("StudentID.add", student);
sqlSession.commit();
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();
throw e;
}finally{
MybatisUtil.closeSqlSession();
}
}
public static void main(String[] args) throws Exception {
StudentDao studentDao = new StudentDao();
Student student = new Student(3, "zhong3", 10000D);
studentDao.add(student);
}
}
#{}和${}的区别
-
#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号);防止sql注入。
-
${} 就是字符串替换。对传递进来的参数原样拼接在SQL中。一般用于传入数据库对象。
MyBatis Like 模糊查询有几种方式
方式1:$ 这种方式,简单,但是无法防止SQL注入,所以不推荐使用
LIKE '%${name}%'
方式2:#
LIKE "%"#{name}"%"
方式3:字符串拼接
AND name LIKE CONCAT(CONCAT('%',#{name},'%'))
方式4:bind标签
方式5:java代码里写
param.setUsername("%CD%"); 在 java 代码中传参的时候直接写上
Mybatis是如何进行分页的?分页插件的原理
实现分页功能
- 原始方法,使用 limit,需要自己处理分页逻辑:
对于 mysql 数据库可以使用 limit ,如:
select * from table limit 5; --返回前5行
select * from table limit 0,5; --同上,返回前5行
select * from table limit 5,10; --返回6-15行
-
拦截StatementHandler,其实质还是在最后生成limit语句
-
使用PageHelper插件
实现分页方式
数组分页
查询出全部数据,然后再list中截取需要的部分。
mybatis接口
List<Student> queryStudentsByArray();
xml配置文件
<select id="queryStudentsByArray" resultMap="studentmapper">
select * from student
</select>
service
// 接口
List<Student> queryStudentsByArray(int currPage, int pageSize);
// 实现接口
@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
//查询全部数据
List<Student> students = studentMapper.queryStudentsByArray();
//从第几条数据开始
int firstIndex = (currPage - 1) * pageSize;
//到第几条数据结束
int lastIndex = currPage * pageSize;
return students.subList(firstIndex, lastIndex); //直接在list中截取
}
controller
@ResponseBody
@RequestMapping("/student/array/{currPage}/{pageSize}")
public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);
return student;
}
sql分页
mybatis接口
List<Student> queryStudentsBySql(Map<String,Object> data);
xml文件
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
select * from student limit #{currIndex} , #{pageSize}
</select>
service
// 接口
List<Student> queryStudentsBySql(int currPage, int pageSize);
// 实现类
public List<Student> queryStudentsBySql(int currPage, int pageSize) {
Map<String, Object> data = new HashedMap();
data.put("currIndex", (currPage-1)*pageSize);
data.put("pageSize", pageSize);
return studentMapper.queryStudentsBySql(data);
}
拦截器分页
创建拦截器,拦截mybatis接口方法id以ByPage结束的语句
RowBounds分页
适用于数据量小
逻辑分页:一次性查询很多数据,然后在数据中再进行检索。
物理分页和逻辑分页
物理分页:直接从数据库中拿出我们需要的数据
例如在Mysql中使用limit;通过PageHelper插件
hibernate采用的是物理分页
逻辑分页:从数据库中拿出所有符合要求的数据,然后再从这些数据中拿到我们需要的分页数据。
例如:mybatis的RowBounds
优缺点
物理分页每次都要访问数据库,逻辑分页只访问一次。
物理分页占用内存少,逻辑分页相对较多。
物理分页数据每次都是最新的,逻辑分页有可能滞后。
//rowBounds分页
List<User> pageByRowBounds();
<select id="pageByRowBounds" resultType="user">
select * from tb_user
</select>
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = 2147483647;
public static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds() {
this.offset = 0;
this.limit = 2147483647;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return this.offset;
}
public int getLimit() {
return this.limit;
}
}
//使用RowBounds分页
@Test
public void pageByBounds(){
SqlSession sqlSession = sqlSessionFactory.openSession();
RowBounds rowBounds = new RowBounds(0, 2);
List<User> list = sqlSession.selectList("dao.UserMapper.pageByRowBounds", null, rowBounds);
list.stream().forEach(System.out::println);
sqlSession.close();
}
MyBatis接口绑定的几种方式
1、SQL语句比较简单:使用注解,在接口的方法上面添加@Select@Update等注解,里面写上对应的SQL语句进行SQL语句的绑定。
2、SQL语句比较复杂:通过映射文件xml方式进行绑定,指定xml映射文件中的namespace对应的接口的全路径名
通常一个mapper.XML对应一个DAO接口,DAO不可以重载
在mapper.xml文件中配置很多的sql语句,执行每个sql语句时,封装为MappedStatement对象,mapper.xml以statement为单位管理sql语句
Statement的实际位置就等于namespace+StatementId
Dao 接口即 Mapper 接口。
- Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。
- Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement
- Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
- Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
规范写法
1、mapper.xml中namespace指定为mapper接口的全限定名
- 此步骤目的:通过mapper.xml和mapper.java进行关联。
2、mapper.xml中statement的id就是mapper.java中方法名
3、mapper.xml中statement的parameterType和mapper.java中方法输入参数类型一致
4、mapper.xml中statement的resultType和mapper.java中方法返回值类型一致.
com.mybatis3.mappers.StudentDao.findStudentById
namespace 为 com.mybatis3.mappers.StudentDao
id 为findStudentById 的 MapperStatement
MyBatis 传递多个参数
占位符
-
在映射文件中使用#{0},#{1}代表传递进来的第几个参数
-
使用@param注解:来命名参数
-
受到参数个数(n)的影响。当 n≤5 时,这是最佳的传参方式
-
#{0},#{1}
方式
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">
select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
</select>
- @param注解方式
public interface usermapper {
user selectuser(@param(“username”) string username,
@param(“hashedpassword”) string hashedpassword);
}
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
Map集合
Map<String, Object> map = new HashMap();
使用 map 传递参数导致了业务可读性的丧失,导致后续扩展和维护的困难,在实际的应用中要果断废弃这种方式。
Java Bean
当参数个数多于5个时,建议使用 Java Bean 方式
public List<Role> findRolesByBean(RoleParams roleParam);
<select id="findRolesByBean" parameterType="com.xc.pojo.RoleParams" resultType="role">
select id, role_name as roleName, note from t_role where role_name like concat('%', #{roleName}, '%') and note like concat('%', #{note}, '%')
</select>
主键生成策略
通过LAST_INSERT_ID()获取刚插入记录的自增主键值,在insert语句执行后,执行select LAST_INSERT_ID()就可以获取自增主键。
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO USER(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>
resultType和resultMap
resultType
指定输出结果的类型(pojo、简单类型、hashmap…),将sql查询结果映射为java对象 。
只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
- 作用:将查询结果按照sql列名pojo属性名一致性映射到pojo中。
- 场合:常见一些明细记录的展示,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
resultMap
将sql查询结果映射为java对象,如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
- 使用association和collection完成一对一和一对多高级映射。
- association
- 作用:将关联查询信息映射到一个pojo类中。
- 场合:为了方便获取关联信息可以使用association将关联订单映射为pojo,比如:查询订单及关联用户信息。
- collection:
- 作用:将关联查询信息映射到一个list集合中。
- 场合:为了方便获取关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块和功能,可使用collection将模块和功能列表映射到list中。
延迟加载 lazyLoadingEnabled=true|false
定义
在进行数据查询时,为了提高数据库查询性能,尽量使用单表查询,因为单表查询比多表关联查询速度要快。
如果查询单表就可以满足需求,一开始先查询单表,当需要关联信息时,再关联查询,当需要关联信息再查询这个叫延迟加载。
延迟加载的条件
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),Mybatis仅支持association关联对象和collection关联集合对象的延迟加载
延迟加载的好处
先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
延迟加载的实例
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
开启延迟加载
-
Mybatis的延迟加载功能默认是关闭的
-
需要在mybatis-config.xml文件中通过setting标签配置来开启延迟加载功能
-
开启延迟加载的属性:lazyLoadingEnabled:全局性设置加载。如果设为‘false’,则所有相关联的都会被初始化加载。默认为false
MyBatis的缓存机制(缓存机制减轻数据库压力,提高数据库性能)
只在查询时配置
缓存的意义将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
一级缓存
一级缓存为 SqlSession
缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。
不同的SqlSession之间缓存数据区域(HashMap)是互相不影响的。
具体流程:
第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来
第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。
Mybatis默认开启一级缓存,不需要进行任何配置。
二级缓存
二级缓存是 mapper
级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域
二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存
<!-- 全局配置参数 -->
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
具体流程:
1.当一个
sqlseesion
执行了一次select
后,在关闭此session
的时候,会将查询结果缓存到二级缓存2.当另一个
sqlsession
执行select
时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
刷新缓存
缓存其实就是为我们的查询服务的,对于增删改而言,如果我们的缓存保存了增删改后的数据,那么再次读取时就会读到脏数据了!
ehcache
ehcache是专门用于管理缓存的,Mybatis的缓存交由ehcache管理
在mybatis中提供一个cache接口,只要实现cache接口就可以把缓存数据灵活的管理起来