ORM框架(object relational mapping)
官方文档: https://mybatis.org/mybatis-3/zh/index.html
原理
执行流程
- 应用程序调用Mybatis框架接口要数据
- mbatis找到数据库中找要数据
- 在项目资源路径下找
mybatis-config.xml配置文件
定位哪个数据库, 找到mappers配置文件 - 通过
mappers配置文件
找到对应sql标签, 根据parameterType属性 传参 执行语句 - 基于sql标签的 resultMap 属性, 把返回的数据库记录封装在Javabean对象中
- 把多个查询结果对象装在一个结果集合中
- 在项目资源路径下找
- 返回一个结果集合
-
SqlSession
接口
SqlSession代表一种可关闭的连接,表示的是数据库客户端和数据库服务端之间的会话,用来连接数据库,执行sql语句- 默认实现类:DefaultSqlSession
- 常用实现类:
SqlSessionTemplate
(纳入了spring bean生命周期的管理)
-
SessionFactory
接口
是生产 SqlSession 的工厂,用来打开SqlSession会话- 默认实现类:DefaultSqlSessionFactory
每次打开-关闭SqlSession 浪费资源,使用数据库连接池:一次连接用完 关闭SqlSession实例时,只是把数据库连接对象放回到对象池中,并没有直接销毁
SqlSessionFactoryBean
是生产 SqlSessionFactory 的工厂bean
1. 数据库 配置
构建 SqlSessionFactory
https://mybatis.org/mybatis-3/zh/configuration.html
作用: 配置 数据库连接信息 与 mappers配置文件地址
XMLConfigBuilder类解析xml文件,把configuration标签信息保存在 Configuration类对象里,environment 标签信息保存在Configuration.Environment 对象里
- 2种方式配置
- mybatis-config.xml 配置文件
- application.properties + @Mapper接口 (spring-boot项目)
//配置文件路径
String resource = "path/mybatis-config.xml";
// 构建 SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通过 SqlSession 实例来直接执行已映射的 SQL 语句
SqlSession session = sqlSessionFactory.openSession();
作用域(Scope)和生命周期
- 依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期
- SqlSessionFactoryBuilder
一旦方法创建了 SqlSessionFactory 就不再需要它, 因此最佳作用域是 方法作用域 - SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在, 最佳作用域是 应用作用域, 最简单的就是使用单例模式或者静态单例模式 - SqlSession
每个线程都应该有它自己的 SqlSession 实例, 线程不安全, 最佳的作用域是 请求或方法作用域 , 最好是HTTP 请求相似的作用域中, 每次收到 HTTP 请求就打开一个 SqlSession返回响应就关闭 - 映射器实例
从技术层面上来讲,映射器实例的最大作用域与请求它们的 SqlSession 相同。但映射器实例应该在调用它们的方法中被获取,使用完毕之后丢弃, 所以最佳的作用域是 方法作用域
事物管理
在 MyBatis 中有两种类型的事务管理器( type="[JDBC | MANAGED]"):
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为
settings标签 设置缓存
Mybatis缓存是基于PerpetualCache 的 HashMap本地缓存
一级缓存 localCacheScope
一级缓存 默认开启
一级缓存的共享范围就是一个SqlSession内部
mybatis-config.xml 配置文件
<settings>
<setting name="localCacheScope" value="SESSION | STATEMENT"/>
同一个session 查询两次相同的对象。第一次会去数据库中取数据,但是第二次就不会访问数据库了,而是直接从session中取出来。
一级缓存失效情况:
- sqlSession不同。
- sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
- sqlSession相同,两次查询之间作用域(一级缓存Session)进行了 C/U/D 操作
- sqlSession相同,手动清除了一级缓存(Session flush 或 close)
二级缓存 cacheEnabled
二级缓存作用域是SessionFactory,
可以在多个 SqlSession 直接共享缓存(在 SqlSession 关闭或提交 之后才会生效)
mybatis-config.xml 配置文件 <setting>标签
<settings>
<setting name="cacheEnabled" value="true"/>
对应的mappers映射器配置 <cache>标签
<mapper namespace="me.gacl.mapping.userMapper">
<!-- 开启二级缓存这个必须开启,全部使用默认的配置-->
<cache/>
//TODO:cache标签详细用法
如果两次查询基于同一个SessionFactory,那么就从二级缓存中取数据,而不用到数据库里去取了
// 同一个 SessionFactory
InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactoryBuilder builderObj = new SqlSessionFactoryBuilder();
factory = builderObj.build(is);
//第一次查询 得到结果之后,mybatis自动将查询结果放入到当前用户的一级缓存
sqlSession1 = factory.openSession();
UserDao dao = sqlSession.getMapper(userDao.class);
User user = dao.findByUsertNo(1);
// sqlSession关闭或提交, 触发从当前一级缓存中将User对象保存到二级缓存
sqlSession.commit();
sqlSession.close();
// 同SessionFactory第二次查询
SqlSession session2 = factory.openSession();
UserDao dao = sqlSession.getMapper(userDao.class);
User user = dao.findByUsertNo(1);
延迟加载
一对多查询时, 平时只查询"一", 用到多时在查询"多"
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 将积极加载改为消息加载即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 原理
使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法
调用 a.getB().getName(), 拦截器先把 B 查询回来
2. mappers映射器 配置
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
作用: 配置 mappers映射器,sql语句,输入输出对象信息
XMLMapperBuilder类会解析这个配置,拿到sql语句
-
映射器可以使用2中形式
- mapper文件(xxxMapper.xml)
- mapper接口(interface xxxMapper.java)
-
多种配置方式
- 实体类 + mapper文件
mybatis-config.xml 里<mapper resource="">
指向 Mapper.xml文件
mapper.xml 的namespace为 实体类 - 实体类 + Mapper接口
mybatis-config.xml 里<mapper class="">
指向 Mapper.java接口
实体类的Mapper接口里 用注解配置sql - 实体类 + mapper文件 + Mapper接口
mybatis-config.xml 里<mapper resource="">
指向 Mapper.xml文件
mapper.xml 的namespace为 Mapper接口
- 实体类 + mapper文件
1. 实体类对象
// 种类对象, 每个种类包含多个产品
public class Category {
private int id;
private String name;
List<Product> products;
}
// 产品对象
public class Product {
private int id;
private String name;
private float price;
private Category category;
}
2. mapper 配置-XML方式
<mapper namespace="com.CategoryMapper">
1.1 查询所有
<!--
List<Category> cs = session.selectList("listCategory");
-->
<select id="listCategory" resultType="Category">
select * from category_
</select>
1.2 一对一查询
<!--
Category c= session.selectOne("getCategory",3);
-->
<select id="getCategory" parameterType="_int" resultType="Category">
select * from category_ where id= #{id}
</select>
1.3 多条件查询
<!--
Map<String,Object> params = new HashMap<>();
params.put("id", 3);
params.put("name", "cat");
List<Category> cs = session.selectList("listCategoryByIdAndName",params);
-->
<select id="listCategoryByIdAndName" parameterType="map" resultType="Category">
select * from category_ where id> #{id} and name like concat('%',#{name},'%')
</select>
2 增加
<!--
Category c = new Category();
c.setName("新增加的Category");
//调用addCategory对应的SQL语句,#{name}会自动获取c对象的name属性值
session.insert("addCategory",c);
-->
<insert id="addCategory" parameterType="Category" >
insert into category_ ( name ) values (#{name})
</insert>
3 删除
<!--
Category c = new Category();
c.setId(6);
session.delete("deleteCategory",c);
-->
<delete id="deleteCategory" parameterType="Category" >
delete from category_ where id= #{id}
</delete>
4 修改
<!--
Category c= session.selectOne("getCategory",3);
c.setName("修改了的Category名称");
session.update("updateCategory",c);
-->
<update id="updateCategory" parameterType="Category" >
update category_ set name=#{name} where id=#{id}
</update>
</mapper>
<!--
session.commit();
session.close();
-->
查询参数映射 parameterType
参数类型(parameterType)
几乎总是可以根据参数对象的类型确定 javaType,除非该对象是一个 HashMap。这个时候,你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用。
查询结果映射 resultMap
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。
ResultMap 就是 MyBatis 想做一种数据库映射模式,完美适配所有的应用程序
隐式配置 resultType
用 resultType 属性, 不需要显式配置 也可以使用
直接写在 resultType , MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上
- 如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名
<select id="listCategory" resultType="Category">
select
user_id as "id",
user_name as "name",
user_products as "products"
from category_
</select>
显式配置 resultMap
resultMap 显式配置
一对一, 多对一, 一对多, 多对多
- 返回值个数
- selectOne()查询 返回0或1条
- selectList()查询 返回0或n条
- 结果对象的关系
关联
-association标签
处理对一的关系集合
-collection标签
处理对多的关系
关联
1.嵌套 Select 查询
执行步骤
1. 执行第一个语句 selectBlog 来获取结果的一个列表
2. 根据列表的每条记录 执行第二个语句 selectAuthor , 来为每条记录加载详细信息
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
2.嵌套结果映射
多个Product对同一个Category
<?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.how2java.pojo">
<resultMap type="Product" id="productBean">
<id column="pid" property="id" />
<result column="pname" property="name" />
<result column="price" property="price" />
<!-- 多对一的关系 -->
<!-- property: 指的是属性名称, javaType:指的是属性的类型 -->
<association property="category" javaType="Category">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</association>
</resultMap>
<!-- 根据id查询Product, 关联将Orders查询出来 -->
<select id="listProduct" resultMap="productBean">
SELECT
c.id AS 'cid',
c.name AS 'cname',
p.id AS 'pid',
p.name AS 'pname'
p.price AS 'price'
FROM category_ c
LEFT JOIN product_ p
ON c.id = p.cid
</select>
</mapper>
集合
1.嵌套 Select 查询
<resultMap id="blogResult" type="Blog">
<!-- 意思是: posts 是一个存储 Post 的 ArrayList 集合 -->
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
2.嵌套结果映射
对多 一个Category多个Product
<?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.how2java.pojo">
<!-- 显式配置resultMap -->
<resultMap type="Category" id="categoryBean">
<id column="cid" property="id" />
<result column="cname" property="name" />
<!-- 一对多的关系 -->
<!-- property: 指的是集合属性的值, ofType:指的是集合中元素的类型 -->
<collection property="products" ofType="Product">
<id column="pid" property="id" />
<result column="pname" property="name" />
<result column="price" property="price" />
</collection>
</resultMap>
<!-- resultType 属性换为 resultMap 属性 -->
<!-- 关联查询分类和产品表 -->
<select id="listCategory" resultMap="categoryBean">
SELECT
c.id AS 'cid',
c.name AS 'cname',
p.id AS 'pid',
p.name AS 'pname'
p.price AS 'price'
FROM category_ c
LEFT JOIN product_ p
ON c.id = p.cid
</select>
</mapper>
- 多对多 (就是 一对多 里嵌套 多对一)
<!-- 多对多的关系 -->
<resultMap type="Category" id="CategoryBean">
<id column="cid" property="id" />
<result column="cname" property="name" />
<!-- 一对多的关系 -->
<collection property="products" ofType="product">
<id column="pid" property="id" />
<result column="pname" property="name" />
<result column="price" property="price"/>
<!-- 多对一的关系 -->
<association property="category" javaType="Category">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</association>
</collection>
</resultMap>
ResultSet
动态sql
https://mybatis.org/mybatis-3/zh/dynamic-sql.html
SQL配置文件里可以写判断语句, 动态生成sql
Java使用
注解实现
创建一个Mapper接口:listCategory,并在其中声明对应的操作方法
public interface listCategory {
@Select("select * from category_")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "products", javaType = List.class, column = "id", many = @Many(select = "com.mapper.ProductMapper.listByCategory") )
})
public User listCategory();
}
这样 与 Category.java 对应的XML配置文件:Category.xml 就可以省略,不用创建
就是把sql信息从xml文件配置移到注解上
Executor 执行器
MyBatis 有三种基本的 Executor 执行器,
- SimpleExecutor
每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象 - ReuseExecutor
执行 update 或 select,以 sql 作为 key 放置于 Map<String, Statement>内, 重复使用 Statement 对象 - BatchExecutor
执行 update, 将所有 sql 都addBatch()添加到批处理中, 统一执行executeBatch() ,缓存了多个 Statement 对象 与 JDBC 批处理相同。
可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。指定 Executor 执行器
日志
https://mybatis.org/mybatis-3/zh/logging.html
分页
自己写SQL分页, 或者用PageHelper分页插件
导入jar包: pagehelper-5.1.0-beta2.jar,jsqlparser-1.0.jar
在mybatis-config.xml中,添加配置开启PageHelper插件
- 分页插件的基本原理
使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
select _ from student
--拦截 sql 后重写为:
select t._ from (select * from student)t limit 0,10
使用案例
- 创建javabean对象,用于映射数据库表数据
public class Category {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 主配置文件mybatis-config.xml
<configuration>
<!-- 自动扫描包路径下的类型 resultType就是对应这个-->
<typeAliases>
<package name="javabean对象包路径"/>
</typeAliases>
<!-- 数据库配置 -->
<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://localhost:3306/how2java?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射Category.xml地址 -->
<mappers>
<mapper resource="映射文件.xml"/>
</mappers>
</configuration>
- 配置映射文件.xml
<!-- 表示命名空间是javabean对象包路径 -->
<mapper namespace="dao接口完整的包名和接口名">
<!-- id: listCategory进行标示以供代码调用。resultType="Category"表示返回的数据和javabean关联起来 -->
<select id="listCategory" resultType="javabean类路径">
<!-- 定义了一条sql语句 -->
select * from category_
</select>
</mapper>
- 测试类
可封装为 Dao 接口
public class TestMybatis {
public static void main(String[] args) throws IOException {
//根据配置文件mybatis-config.xml得到sqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//根据sqlSessionFactory 得到session
SqlSession session = sqlSessionFactory.openSession();
//***通过session的selectList方法,调用id=listCategory的sql语句
List<Category> cs = session.selectList("listCategory");
//得到Category集合,遍历
for (Category c : cs) {
System.out.println(c.getName());
}
}
}
Mybatis Generator 逆向工程
https://blog.csdn.net/xyc1211/article/details/115248989
Mybatis Generator是一个用于Mybatis逆向工程的工具。
前面的方式都是先有pojo, mapper, xml, 然后再创建表。
用逆向工程的方式,首先保证数据库里有表,然后通过Mybatis Generator生成pojo, mapper和xml。
可以节约大家的时间,提高开发效率,降低出错几率
多对一,一对多需要自己手写,这个工具(版本1.3.5)不提供
题库
https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/mybatis/mybatis-interview.md