MyBatis
什么是MyBatis
-
MyBatis是优秀的持久层框架
-
MyBatis使用XML将SQL与程序解耦,便于维护
-
MyBatis学习简单,执行高效,是JDBC的延伸
1.MyBatis开发流程
-
引入MyBatis依赖
-
创建核心配置文件
-
创建实体(Entity)类
-
创建Mapper映射文件
-
初始化SessionFactory
-
利用SqlSession对象操作数据
1.1引入MyBatis依赖
利用maven直接从仓库导入即可,有的小伙伴肯定不知道怎么去官网找,先来教一下好了。
官网链接:Maven Repository: maven (mvnrepository.com)
直接搜索mybatis然后点击第一个就好了
这里我们选择3.5.1版本
往下滑我们可以看到他的配置信息,复制到pom.xml即可
关于配置pom.xml时IDEA报错解决方案
有时候我们的idea会抽风,复制过来之后会报红或者显示无法在中央仓库中找到依赖,一般剪切掉它再贴一遍就好了,实在不行重启一下idea
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>Mybatis</artifactId>
<version>3.5.1</version>
</dependency>
//mysql包顺便也放这里了,如果使用mysql的同学记得加上
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
1.2创建核心配置文件
差不多和Spring的配置文件差不多格式
<?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://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="20030515"/>
</dataSource>
</environment>
<!-- 在这里可以配置多个环境,比如生产环境,只需要切换第8行的default为每个环境的id即可-->
<environment id="produce">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="20030515"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--用来配置mapper的xml配置文件,相当于告诉mybatis我的用于sql语句的配置文件放哪里-->
<mapper resource="mapper/ajaxdb.xml"/>
</mappers>
</configuration>
1.3创建实体(Entity)类
这里不再赘述,跟之前的JDBC连接数据库时要创建出一个跟表中字段相同的类是一样的。韩顺平Spring体系化笔记(内含ioc,aop,动态代理等底层原理) - 翰林猿 - 博客园 (cnblogs.com)
具体可以查看本文中第10小节,不过既然都学到了mybatis想必对此熟悉的不能再熟悉了。
1.4创建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">
<!--namespace相当于一个包名,这个包下放了很多种sql语句,每种又有自己的id,
比如说到时候要使用select * from user时就直接填入user.selectAll即可-->
<mapper namespace="user">
<!--id 相当于给这段用于select的sql语句取一个别名,到时候直接用selectAll代替 select * from user 这句话-->
<!--resultType中填写entity类的全路径-->
<select id="selectAll" resultType="entity.ajaxdbEntity">
select * from user;
</select>
</mapper>
1.5初始化SessionFactory
在这里我们顺便编写一个工具类
package utils;
import entity.ajaxdbEntity;
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.Test;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
/**
* @Author: 翰林猿
* @Description: TODO
**/
public class MyBatisUtils {
/**
* MyBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
*/
//利用static(静态)属于类不属于对象,且全局唯一
private static SqlSessionFactory sqlSessionFactory = null;
//利用静态块在初始化类时实例化sqlSessionFactory
static {
Reader reader = null;
try {
这里的Resources
reader = Resources.getResourceAsReader("mybatisConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
//初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
throw new ExceptionInInitializerError(e);
}
}
/**
* openSession 创建一个新的SqlSession对象
* SqlSession对象类似于JDBC中的Connection
* @return SqlSession对象
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
/**
* 释放一个有效的SqlSession对象
*类似于JDBC中释放获取到的Connection
* @param session 准备释放SqlSession对象
*/
public static void closeSession(SqlSession session) {
if (session != null) {
session.close();
}
}
//简单测试一下
@Test
public void test(){
//类似于获取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectAll");
for (ajaxdbEntity dao : ajaxdbDaoList) {
System.out.println(dao.getId());
System.out.println(dao.getUsername());
}
}
}
mybatis底层实现(造轮子)
2.SQL
SQL传参及查询
回忆之前的JDBC中在组织sql语句时 使用问号动态传入参数 进行查询的操作,同样我们mybatis也有。
<!--parameterType也就是传入的参数的类型是什么-->
<select id="selectList" resultType="entity.ajaxdbEntity" parameterType="Map">
select * from user
where id between #{min} and #{max}
order by id;
</select>
这里我们使用map的形式作为参数传递,那么怎么使用呢,修改一下我们的 Test 函数作为示例。
@Test
public void test(){
//类似于获取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
HashMap map = new HashMap();
map.put("min",1);
map.put("max",3);
//mybatis底层会将传入的map解析,找到#{key}对应的value填入sql语句中
List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectList",map);
for (ajaxdbEntity map : ajaxdbDaoList) {
System.out.println(map);
}
}
多表查询
多表查询时我们不再需要填入parameterType,而且resultType使用Map或者LinkHashMap
使用Map的话,查询出来的字段顺序是混乱的,具体看Map的底层原理,而LinkHashMap是按顺序的。
但是这种查询方式有一个很大的缺点,你应该也已经发现了,为什么我们的resultType不再是实体Entity类了?
这种查询方式不需要经过验证,他什么东西都可以直接查询出来。所以在企业中开发大型项目还是一般不使用这种方式。
<select id="selectTwice" resultType="Map">
select * from user,user2;
</select>
所以我们使用ResultMap结果映射,来解决一下这个问题。
ResultMap结果映射查询
作用:首先是完成数据库字段与实体类字段的映射(类似之前为了解决数据库字段和实体类字段不相同采用AS语句完成映射),其次就是解决上面所提到的问题。
说白了,还是Entity那套,再创建一个类,作为多表查询的实体类,只不过这个类里有多个entity的字段罢了。那么我们来写一下这个字段吧。
package entity;
/**
* @Author: 翰林猿
* @Description: 多表查询实体类
**/
public class UserDTO {
private user user = new user(); //user表
private String cn; //user2表中的cn字段(ChineseName的缩写)
@Override
public String toString() {
return "UserDTO{" +
"user=" + user +
", cn='" + cn + '\'' +
'}';
}
public user getUser() {
return user;
}
public void setUser(user user) {
this.user = user;
}
public String getCn() {
return cn;
}
public void setCn(String cn) {
this.cn = cn;
}
public UserDTO() {
}
public UserDTO(user ajaxdbEntity, String cn) {
user = ajaxdbEntity;
this.cn = cn;
}
}
好,我们再来看看基本使用方法,我们要定义一个resultMap标签,id值就是作为这段resultMap的别名,type是最后查询返回的类的全路径,然而里面又有很多种标签,这里我们就介绍2个,其他的请大家自己前往mybatis官网查看教程。
我们的id和result标签中又包括两个参数,property以及column
-
property :填写的必须与上面的type中的类的字段完全相同,(也就是UserDTO中的字段),其中有一个字段是user类的,那么为了拿到user类中的字段直接使用 . 运算符即可。
-
column :数据库中的列名,或者是列的别名。
<resultMap id="selectDTO" type="entity.UserDTO">
<id property="user.id" column="id"></id>
<result property="user.username" column="username"/>
<result property="user.pwd" column="pwd"/>
<result property="user.email" column="email"/>
<result property="cn" column="cn"/>
property对应实体类的属性 ,colum对应数据库的字段
</resultMap>
<select id="selectTwice" resultMap="selectDTO">
select user.* , cn from user,user2;
</select>
/**
* @Author: 翰林猿
* @Description: 多表查询实体类,测试
**/
@Test
public void test(){
//类似于获取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
List<UserDTO> ajaxdbDaoList = sqlSession.selectList("User.selectTwice");
for (UserDTO map : ajaxdbDaoList) {
System.out.println(map);
}
}
SQL插入
我们在写sql插入语句的时候有时候要写很多列名,真的是非常令人烦恼啊,很显然,mybais开发人员也想到了这点,用foreach标签节约开发时间。
foreach元素的属性主要有 item,index,collection,open,separator,close。
-
item表示集合中每一个元素进行迭代时的别名,
-
index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,
-
open表示该语句以什么开始,
-
separator表示在每次进行迭代之间以什么符号作为分隔符,
-
close表示以什么结束。
<!--INSERT INTO table-->
<!--VALUES ("a" , "a1" , "a2"),("b" , "b1" , "b2"),(....)-->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
</foreach>
</insert>
注意新增数据之后,要记得提交事务
/**
* 新增数据,示范用例
*/
@Test
public void testInsert() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("测试商品");
goods.setSubTitle("测试子标题");
goods.setOriginalCost(200f);
goods.setCurrentPrice(100f);
goods.setDiscount(0.5f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
//insert()方法返回值代表本次成功插入的记录总数
int num = session.insert("goods.insert", goods);
session.commit();//提交事务数据
System.out.println(goods.getGoodsId());
}catch (Exception e){
if(session != null){
session.rollback();//回滚事务
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
SQL删除
删除同理
<delete id="batchDelete" parameterType="java.util.List">
DELETE FROM t_goods WHERE goods_id in
<foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
/**
* 删除数据
*/
@Test
public void testDelete() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
int num = session.delete("goods.delete" , 739);
session.commit();//提交事务数据
}catch (Exception e){
if(session != null){
session.rollback();//回滚事务
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
动态SQL
在我们逛淘宝的时候,是否有发现在搜索商品时有很多标签可以选择,比如说指定某个品牌,某种类型等等。其实从开发者的角度上来看,是不是还是一个SQL语句罢了,只不过多加了几个参数,但是问题就在于,这些参数如何动态的加载进去,在想要的时候才用他呢?
这里引出mybatis的动态SQL语句技术,不过是加个标签罢了。底层依旧是之前那套,dom4j+动态代理+反射完成的。
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
<where> //SQL种的where改成使用where标签
<if test="categoryId != null"> //如果map里有这个参数,就加上下面这句
and category_id = #{categoryId}
</if>
<if test="currentPrice != null">
and current_price < #{currentPrice}
</if>
</where>
</select>
/**
* 动态SQL语句
*/
@Test
public void testDynamicSQL() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Map param = new HashMap();
param.put("categoryId", 44);
param.put("currentPrice", 500);
//查询条件
List<Goods> list = session.selectList("goods.dynamicSQL", param);
for(Goods g:list){
System.out.println(g.getTitle() + ":" +
g.getCategoryId() + ":" + g.getCurrentPrice());
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
3.Mybatis二级缓存机制
为了提高查询效率,减少数据库的访问次数,Mybatis采用了两层缓存机制,并分为一级缓存和二级缓存
因为一级缓存的命中率可能较低,所以还有一层二级缓存
-
一级缓存默认开启,缓存范围SqlSession
-
(换句话说就是,只在SqlSession session = MyBatisUtils.openSession()的这个session里有效,当我们写了两遍这个代码,就是两个不同的SqlSession对象)
-
拿上面的代码举例,就是你使用这个session不管查询多少次相同的语句,都是从缓存里拿出来的一个结果
session.selectList("goods.dynamicSQL", param); session.selectList("goods.dynamicSQL", param); session.selectList("goods.dynamicSQL", param); //最后都是相同的结果,内存地址都是一样的
-
-
二级缓存手动开启,属于范围Mapper Namespace
-
(换句话说,就是只要是使用了之前我们定义mapper的namespace中的SQL语句里都有效)
<mapper namespace="goods"> ....各种SQL.... </mapper>
-
注意:
当调用SqlSession的修改、添加、删除、commit()、close()等方法时,为了保证数据的一致性,mybatis会强制清空一级缓存。
如何理解这句话?用代码举例一下
session = MyBatisUtils.openSession();
Goods goods = session.selectOne("goods.selectById" , 1603);
session.commit(); //commit提交时对该namespace缓存强制清空
Goods goods2 = session.selectOne("goods.selectById" , 1603);
这个时候,因为提交过一次事务,所以第二次的查询goods2时的hashcode与第一次的goods其实是不一样的,本质上是2次查询。而不是直接拿出goods已经查询出来的内容赋给goods2
开启二级缓存
在xml中的mapper配置一句话即可
<mapper namespace="goods">
<!--
eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
1.LRU – 最近最久未使用:移除最长时间不被使用的对象。
O1 O2 O3 O4 .. O512 最多只有512个对象
14 99 83 1 893(秒) 记录对象上一次被访问的时间,如果512个满了,就会先把时间最长的893秒的那个对象移除
2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.SOFT – 软引用:移除基于垃圾收集器状态和软引用规则的对象。
4.WEAK – 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval属性表示刷新缓存的时间间隔ms
size属性表示缓存的大小
readOnly属性表示缓存是否只读,可选值为true或false,true是返回对象本身(效率高),false则是返回对象的副本(安全好)
-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
....各种SQL....
</mapper>
缓存大量数据性能问题
既然我们开启了缓存,那么就要考虑一下如果缓存了大量数据影响性能怎么办,其实对于查询大量数据的语句可以不使用缓存,而且在实际过程中,这种大量的查询也不会经常复用。
<!-- useCache="false"代表该语句不保存至缓存 -->
<select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
select * from t_goods order by goods_id desc limit 10
</select>
<!-- flushCache="true"执行完这句话马上刷新缓存 = 一个commit操作 -->
<!-- 并且这句话查询出来的数据本身也不会放入缓存 -->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
4.对象关联查询(一对多、多对一)
我们知道,一个表内有很多个实体,可能有一个实体对应了很多个对象,有一些则是一对一,多对一,多对多等等。
所以引出我们的关联查询
一对多查询:
先来举个例子:比如说我们要查询一个商品,但是一个商品Goods又对应了很多个商品细节GoodsDetail(一对多)
所以我们的商品细节表GoodsDetail要持有我们的商品表Goods的主键 goodsId,那么除掉建表的过程,先写一下对应的实体类吧
public class Goods {
private Integer goodsId;//商品编号
private String title;//标题
private String subTitle;//子标题
private Float originalCost;//原始价格
private Float currentPrice;//当前价格
private Float discount;//折扣率
private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
private Integer categoryId;//分类编号
private List<GoodsDetail> goodsDetails;
}
//省略getter,setter,构造器等等
public class GoodsDetail {
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
private Goods goods;
}
//省略getter,setter,构造器等等
那么我们要如何实现一对多的查询呢,我们来看看本质是什么,不过就是去Goods里面找GoodsDetail,再去GoodsDetail里面找具体的属性罢了。
所以,还是使用我们的resultMap进行映射就好了,只要将goodsDetails的属性映射到Goods里面,不就相当于Goods拥有了goodsDetails的属性了嘛,可以理解为类似下面这个类。
public class GoodsAfterMapping {
private Integer goodsId;//商品编号
private String title;//标题
private String subTitle;//子标题
private Float originalCost;//原始价格
private Float currentPrice;//当前价格
private Float discount;//折扣率
private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
private Integer categoryId;//分类编号
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
private Goods goods;
}
//省略getter,setter,构造器等等
ok,有了思路,来具体实现一下吧,写好查询语句
<mapper namespace="goodsDetail">
<select id="selectOneToMany" resultMap="RmGoods1">
select * from t_goods limit 0,10
</select>
</mapper>
想一想我们的思路,是不是要利用这句select语句查询出来的ID,去我们的我们商品细节表GoodsDetail里把其他属性也查出来,所以我们再写一个select语句用于查询商品细节表GoodsDetail
<mapper namespace="goodsDetail">
<select id="selectByGoodsId" parameterType="Integer"
resultType="com.imooc.mybatis.entity.GoodsDetail">
select * from t_goods_detail where goods_id = #{value}
</select>
</mapper>
然后开始配置我们的 resultMap 取名为 RmGoods1
<mapper namespace="goodsDetail">
<!--
resultMap可用于说明一对多或者多对一的映射逻辑
id 是resultMap属性引用的标志
type 指向我们所谓的一对多的一的实体(也就是Goods)
-->
<resultMap id="RmGoods1" type="com.imooc.mybatis.entity.Goods">
<!-- 还是先确定主键id,把数据库字段和实体类同步一下 -->
<id column="goods_id" property="goodsId"></id>
<!--这里不需要像多表查询那里大量配置result,因为数据库字段和POJO字段符合驼峰命名规则,框架会自动转换-->
<!--collection的含义是,在select * from t_goods limit 0,1 得到结果后,
将得到的goods_id值,
代入到goodsDetail命名空间的selectByGoodsId的SQL中执行查询,
最后框架会自动将查询得到的"商品细节"的结果(一个集合)
赋值给 private List<GoodsDetail> goodsDetails 这个对象 -->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
</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="goodsDetail">
查询Goods表
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,10
</select>
利用上句查询出来的ID去GoodsDetail查询
<select id="selectByGoodsId" parameterType="Integer"
resultType="com.imooc.mybatis.entity.GoodsDetail">
select * from t_goods_detail where goods_id = #{value}
</select>
配置resultMap
<resultMap id="RmGoods1" type="com.imooc.mybatis.entity.Goods">
<id column="goods_id" property="goodsId"></id>
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
</mapper>
标签:goods,未完结,private,查询,session,笔记,MyBatis,public,user
From: https://www.cnblogs.com/hanlinyuan/p/17430333.html