Mybatis入门
开发我的第一个MyBatis程序
resources目录:
放在这个目录当中的,一般都是资源文件,配置文件。
直接放到resources目录下的资源,等同于放到了类
的根路径下。
开发步骤
第一步:打包方式jar
第二步:引入依赖
mybatis依赖
mysql驱动依赖
第三步:编写mybatis核心配置文件:mybatis-config.xml
注意:
第一:这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。只是大家都采用这个名字。
第二:这个文件存放的位置也不是固定的,可以随意,但一般情况下,会放到类的根路径下。
mybatis-config.xml文件中的配置信息不理解没关系,先把连接数据库的信息修改以下即可。
其他的别动。
第四步:编写XxxxMapper.xml文件
在这个配置文件当中编写SQL语句。
这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.xml
把它暂时放到类的根路径下。
第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:
第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?
SqlSession
SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。
要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。
怎么获取SqlSessionFactory对象呢?
需要首先获取SqlSessionFactoryBuilder对象。
通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。
mybatis的核心对象包括:
SqlSessionFactoryBuilder
SqlSessionFactory
SqlSession
SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession
从 XML 中构建 SqlSessionFactory
通过官方的这句话,你能想到什么呢?
第一:在MyBatis中一定是有一个很重要的对象,这个对象是:SqlSessionFactory对象。
第二:SqlSessionFactory对象的创建需要XML。
XML是什么?
它一定是一个配置文件。
mybatis中有两个主要的配置文件:
其中一个是:mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息等。(一个)
另一个是:XxxxMapper.xml,这个文件是专门用来编写SQL语句的配置文件。(一个表一个)
t_user表,一般会对应一个UserMapper.xml
t_student表,一般会对应一个StudentMapper.xml
关于第一个程序的小细节
mybatis中sql语句的结尾";"可以省略。
Resources.getResourceAsStream
小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)
优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。
InputStream is = new FileInputStream("d:\mybatis-config.xml");
采用这种方式也可以。
缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。
已经验证了:
mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其它名字。
mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其它位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
ClassLoader.getSystemClassLoader() 获取系统的类加载器。
系统类加载器有一个方法叫做:getResourceAsStream
它就是从类路径当中加载资源的。
通过源代码分析发现:
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
底层的源代码其实就是:
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
CarMapper.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗?
都不是固定的。
resource属性:这种方式是从类路径当中加载资源。
url属性:这种方式是从绝对路径当中加载资源。
不建议,移植性差
关于mybatis的事务管理机制。(深度剖析)
在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理
type属性的值包括两个:
JDBC(jdbc)
MANAGED(managed)
type后面的值,只有以上两个值可选,不区分大小写。
在mybatis中提供了两种事务管理机制:
第一种:JDBC事务管理器
第二种:MANAGED事务管理器
JDBC事务管理器:
mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:
conn.setAutoCommit(false); 开启事务。
....业务处理...
conn.commit(); 手动提交事务
使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。
如果你编写的代码是下面的代码:
SqlSession sqlSession = sqlSessionFactory.openSession(true);
表示没有开启事务。因为这种方式压根不会执行:conn.setAutoCommit(false);
在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。
如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。
这种方式实际上是不建议的,因为没有开启事务
MANAGED事务管理器:
mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如:spring。
我不管事务了,你来负责吧。
对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED
那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。
没有人管理事务就是没有事务。也就是不会开启事务,不开启事务就会自动提交。
JDBC中的事务:
如果你没有在JDBC代码中执行:conn.setAutoCommit(false);的话,默认的autoCommit是true。
重点:
以后注意了,只要你的autoCommit是true,就表示没有开启事务。
只有你的autoCommit是false的时候,就表示开启了事务。
关于mybatis集成日志组件。让我们调试起来更加方便。
mybatis常见的集成的日志组件有哪些呢?
SLF4J(沙拉风):沙拉风是一个日志标准,其中有一个框架叫做logback,它实现了沙拉风规范。
LOG4J
LOG4J2
STDOUT_LOGGING:标准日志
....
注意:log4j log4j2 logback都是同一个作者开发的。
其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。
只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。
这个标签在编写的时候要注意,它应该出现在environments标签之前。注意顺序。当然,不需要记忆这个顺序。
因为有dtd文件进行约束呢。我们只要参考dtd约束即可。
这种实现也是可以的,可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。
但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。
集成logback日志框架。
logback日志框架实现了slf4j标准。(沙拉风:日志门面。日志标准。)
第一步:引入logback的依赖。
ch.qos.logback
logback-classic
1.2.11
第二步:引入logback所必须的xml配置文件。
这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。
这个配置文件必须放到类的根路径下。不能是其他位置。
主要配置日志输出相关的级别以及日志具体的格式。
mybatis入门案例:
package com.powernode.mybatis.test;
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.FileInputStream;
import java.io.InputStream;
public class MyBatisIntroductionTest {
public static void main(String[] args) throws Exception {
// 获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 获取SqlSessionFactory对象
InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // Resources.getResourceAsStream默认就是从类的根路径下开始查找资源。
//InputStream is = Resources.getResourceAsStream("com/mybatis.xml");
//InputStream is = new FileInputStream("d:\\mybatis-config.xml");
//InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 一般情况下都是一个数据库对应一个SqlSessionFactory对象。
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(); // 如果使用的事务管理器是JDBC的话,底层实际上会执行:conn.setAutoCommit(false);
// 这种方式实际上是不建议的,因为没有开启事务。
//SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 执行SQL语句
int count = sqlSession.insert("insertCar"); // 返回值是影响数据库表当中的记录条数。
System.out.println("插入了几条记录:" + count);
// 手动提交
sqlSession.commit(); // 如果使用的事务管理器是JDBC的话,底层实际上还是会执行conn.commit();
}
}
mybatis完整版案例
package com.powernode.mybatis.test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisCompleteTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 开启会话(底层会开启事务)
sqlSession = sqlSessionFactory.openSession();
// 执行SQL语句,处理相关业务
int count = sqlSession.insert("insertCar");
System.out.println(count);
// 执行到这里,没有发生任何异常,提交事务。终止事务。
sqlSession.commit();
} catch (Exception e) {
// 最好回滚事务
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 关闭会话(释放资源)
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
编写Mybatis工具类
package com.powernode.mybatis.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;
/**
* MyBatis工具类
* @author 动力节点
* @version 1.0
* @since 1.0
*/
public class SqlSessionUtil {
// 工具类的构造方法一般都是私有化的。
// 工具类中所有的方法都是静态的,直接采用类名即可调用。不需要new对象。
// 为了防止new对象,构造方法私有化。
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
// 类加载时执行
// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件。创建SqlSessionFactory对象。
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/*public static SqlSession openSession(){
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// SqlSessionFactory对象:一个SqlSessionFactory对应一个environment,一个environment通常是一个数据库。
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}*/
/**
* 获取会话对象。
* @return 会话对象
*/
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}
Mybatis的crud
在封装pojo类时,数据库表当中的字段应该和pojo类的属性一一对应。并且建议使用包装类,这样可以防止null的问题。
使用对象传参,底层调用的是get方法,所以如果没有提供get方法则会报错。通过反射机制获取到值。
使用mybatis完成CRUD
什么是CRUD
C: Create增
R: Retrieve查(检索)
U: Update改
D: Delete删
insert
例如:JDBC的代码是怎么写的?
String sql = "insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)";
ps.setString(1, xxx);
ps.setString(2, yyy);
....
在JDBC当中占位符采用的是?,在mybatis当中是什么呢?
和?等效的写法是:#{}
在mybatis当中不能使用?占位符,必须使用 #{} 来代替JDBC当中的 ?
#{} 和 JDBC当中的 ? 是等效的。
java程序中使用Map可以给SQL语句的占位符传值:
Map<String, Object> map = new HashMap<>();
map.put("k1", "1111");
map.put("k2", "比亚迪汉");
map.put("k3", 10.0);
map.put("k4", "2020-11-11");
map.put("k5", "电车");
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{k1},#{k2},#{k3},#{k4},#{k5});
注意:#{这里写什么?写map集合的key,如果key不存在,获取的是null}
一般map集合的key起名的时候要见名知意。
map.put("carNum", "1111");
map.put("brand", "比亚迪汉2");
map.put("guidePrice", 10.0);
map.put("produceTime", "2020-11-11");
map.put("carType", "电车");
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
java程序中使用POJO类给SQL语句的占位符传值:
Car car = new Car(null, "3333", "比亚迪秦", 30.0, "2020-11-11", "新能源");
注意:占位符#{},大括号里面写:pojo类的属性名
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
把SQL语句写成这个德行:
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})
出现了什么问题呢?
There is no getter for property named 'xyz' in 'class com.powernode.mybatis.pojo.Car'
mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。
怎么解决的?
可以在Car类中提供一个getXyz()方法。这样问题就解决了。
通过这个测试,得出一个结论:
严格意义上来说:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。
例如:getUsername() --> #{username}
例如:getEmail() --> #{email}
....
也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?
调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()
delete
需求:根据id删除数据
将id=59的数据删除。
实现:
int count = sqlSession.delete("deleteById", 59);
delete from t_car where id = #{fdsfd}
注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。
update
需求:根据id修改某条记录。
实现:
update t_car set
car_num=#{carNum},
brand=#{brand},
guide_price=#{guidePrice},
produce_time=#{produceTime},
car_type=#{carType}
where
id = #{id}
</update>
Car car = new Car(4L, "9999", "凯美瑞", 30.3, "1999-11-10", "燃油车");
int count = sqlSession.update("updateById", car);
select(查一个,根据主键查询的话,返回的结果一定是一个。)
需求:根据id查询。
实现:
select * from t_car where id = #{id}
Object car = sqlSession.selectOne("selectById", 1);
mybatis底层执行了select语句之后,一定会返回一个结果集对象:ResultSet
JDBC中叫做ResultSet,接下来就是mybatis应该从ResultSet中取出数据,封装java对象。
所以你需要告诉Mybatis要封装成什么对象
需要特别注意的是:
select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成什么类型的java对象。你需要告诉mybatis。
resultType通常写的是:全限定类名。
Car{id=1, carNum='null', brand='宝马520Li', guidePrice=null, produceTime='null', carType='null'}
输出结果有点不对劲:
id和brand属性有值。
其他属性为null。
carNum以及其他的这几个属性没有赋上值的原因是什么?
select * from t_car where id = 1
执行结果:
+----+---------+-----------+-------------+--------------+----------+
| id | car_num | brand | guide_price | produce_time | car_type |
+----+---------+-----------+-------------+--------------+----------+
| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |
+----+---------+-----------+-------------+--------------+----------+
car_num、guide_price、produce_time、car_type这是查询结果的列名。
这些列名和Car类中的属性名对不上。
Car类的属性名:
carNum、guidePrice、produceTime、carType
那这个问题怎么解决呢?
select语句查询的时候,查询结果集的列名是可以使用as关键字起别名的。
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from
t_car
where
id = #{id}
</select>
起别名之后:
+----+--------+-----------+------------+-------------+---------+
| id | carNum | brand | guidePrice | produceTime | carType |
+----+--------+-----------+------------+-------------+---------+
| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |
+----+--------+-----------+------------+-------------+---------+
select(查所有的)
List cars = sqlSession.selectList("selectAll");
注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。
selectList方法:mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。
在sql mapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。
怎么用?
在xml文件中:
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as produceTime,
car_type
from
t_car
</select>
在java程序中的写法:
List cars = sqlSession.selectList("aaaaaaaaa.selectAll");
实际上,本质上,mybatis中的sqlId的完整写法:
namespace.id
命名空间+id唯一确定一条sql语句
注意点:Mybatis的build方法可以通过环境id来指定环境,如果不指定的话就使用mybatis的默认环境
properties标签
<!--java.util.Properties类,是一个Map集合,key和value都是String类型-->
<!--在properties标签可以配置很多属性-->
<properties>
<!--这是其中的一个属性-->
<!--<property name="属性名" value="属性值" />-->
<property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
...
</properties>
<!--引入配置文件-->
<!--resource,一定是从类路径下查找资源-->
<properties resource="文件路径"/>
<!--从绝对路径中加载资源。绝对路径怎么写?file:///路径-->
<properties url="file:///d:/jdbc.properties"/>
动态SQL
<?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.powernode.mybatis.mapper.CarMapper">
<delete id="deleteByIds2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id=#{id}
</foreach>
</delete>
<insert id="insertBatch">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
</foreach>
</insert>
<!--
foreach标签的属性:
collection:指定数组或者集合
item:代表数组或集合中的元素
separator:循环之间的分隔符
open: foreach循环拼接的所有sql语句的最前面以什么开始。
close: foreach循环拼接的所有sql语句的最后面以什么结束。
collection="ids" 第一次写这个的时候报错了,错误信息是:[array, arg0]
什么意思?
map.put("array", 数组);
map.put("arg0", 数组);
-->
<delete id="deleteByIds">
<!--
delete from t_car where id in(
<foreach collection="ids" item="aaaaaaa" separator=",">
#{aaaaaaa}
</foreach>
)
-->
delete from t_car where id in
<!-- 小括号可以省略不写 -->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
<select id="selectByChoose" resultType="Car">
select * from t_car
<where>
<choose>
<when test="brand != null and brand != ''">
brand like "%"#{brand}"%"
</when>
<when test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice}
</when>
<otherwise>
car_type = #{carType}
</otherwise>
</choose>
</where>
</select>
<update id="updateBySet">
update t_car
<set>
<if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
<if test="brand != null and brand != ''">brand = #{brand},</if>
<if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
<if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
<if test="carType != null and carType != ''">car_type = #{carType},</if>
</set>
where
id = #{id}
</update>
<update id="updateById">
update t_car set
car_num = #{carNum},
brand = #{brand},
guide_price = #{guidePrice},
produce_time = #{produceTime},
car_type = #{carType}
where
id = #{id}
</update>
<select id="selectByMultiConditionWithTrim" resultType="Car">
select * from t_car
<!--
prefix:加前缀
suffix:加后缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀
-->
<!--prefix="where" 是在trim标签所有内容的前面添加 where-->
<!--suffixOverrides="and|or" 把trim标签中内容的后缀and或or去掉-->
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" or
</if>
<if test="guidePrice != null and guidePrice != ''">
guide_price > #{guidePrice} and
</if>
<if test="carType != null and carType != ''">
car_type = #{carType}
</if>
</trim>
</select>
<select id="selectByMultiConditionWithWhere" resultType="Car">
select * from t_car
<!--where标签是专门负责where子句动态生成的。-->
<!-- where标签的作⽤:让where⼦句更加动态智能。
所有条件都为空时,where标签保证不会⽣成where⼦句。
⾃动去除某些条件前⾯多余的and或or。
where标签不能把后面的and去掉-->
<where>
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</where>
</select>
<select id="selectByMultiCondition" resultType="Car">
select * from t_car where 1 = 1
<!--
1. if标签中test属性是必须的。
2. if标签中test属性的值是false或者true。
3. 如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。
4. test属性中可以使用的是:
当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param("brand"),那么这里只能使用brand
当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2....
当使用了POJO,那么test中出现的是POJO类的属性名。
5. 在mybatis的动态SQL当中,不能使用&&,只能使用and。
-->
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
</if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price > #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</select>
<!-- sql标签⽤来声明sql⽚段
include标签⽤来将声明的sql⽚段包含到某个sql语句当中
作⽤:代码复⽤。易维护。 -->
<sql id="carCols">id,car_num carNum,brand,guide_price guidePrice,produce_t
ime produceTime,car_type carType</sql>
<select id="selectAllRetMap" resultType="map">
select <include refid="carCols"/> from t_car
</select>
<select id="selectAllRetListMap" resultType="map">
select <include refid="carCols"/> carType from t_car
</select>
<select id="selectByIdRetMap" resultType="map">
select <include refid="carCols"/> from t_car where id = #{id}
</select>
</mapper>
高级映射
一张表的数据和JVM中的对象进行映射叫基本映射。
多张表和JVM中的对象进行映射叫高级映射。
多对一关系映射
<?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.powernode.mybatis.mapper.StudentMapper">
<select id="selectByCidStep2" resultType="Student">
select * from t_stu where cid = #{cid}
</select>
<!--
分步查询的优点:
第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。
什么是延迟加载(懒加载),有什么用?
延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
在mybatis当中怎么开启延迟加载呢?
association标签中添加fetchType="lazy"
注意:默认情况下是没有开启延迟加载的。需要设置:fetchType="lazy"
这种在association标签中配置fetchType="lazy",是局部的设置,只对当前的association关联的sql语句起作用。
在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true
实际开发中的模式:
把全局的延迟加载打开。
如果某一步不需要使用延迟加载,请设置:fetchType="eager"
-->
<!--两条SQL语句,完成多对一的分步查询。-->
<!--这里是第一步:根据学生的id查询学生的所有信息。这些信息当中含有班级id(cid)-->
<resultMap id="studentResultMapByStep" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
<!-- 这里需要指定另外第二步SQL语句的id -->
select="com.powernode.mybatis.mapper.ClazzMapper.selectByIdStep2"
<!-- 这里是要传给第二步的SQL语句的参数 -->
column="cid"
fetchType="eager"/>
</resultMap>
<select id="selectByIdStep1" resultMap="studentResultMapByStep">
select sid,sname,cid from t_stu where sid = #{sid}
</select>
<!--一条SQL语句,association。-->
<resultMap id="studentResultMapAssociation" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<!--
association:翻译为关联。一个Student对象关联一个Clazz对象
property:提供要映射的POJO类的属性名。
javaType:用来指定要映射的java类型。
-->
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
</association>
</resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
select
s.sid,s.sname,c.cid,c.cname
from
t_stu s left join t_clazz c on s.cid = c.cid
where
s.sid = #{sid}
</select>
<!--多对一映射的第一种方式:一条SQL语句,级联属性映射。-->
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select
s.sid,s.sname,c.cid,c.cname
from
t_stu s left join t_clazz c on s.cid = c.cid
where
s.sid = #{sid}
</select>
</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="com.powernode.mybatis.mapper.ClazzMapper">
<!--分步查询第一步:根据班级的cid获取班级信息。-->
<resultMap id="clazzResultMapStep" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus"
select="com.powernode.mybatis.mapper.StudentMapper.selectByCidStep2"
column="cid" fetchType="eager" />
</resultMap>
<select id="selectByStep1" resultMap="clazzResultMapStep">
select cid,cname from t_clazz where cid = #{cid}
</select>
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--一对多,这里是collection。collection是集合的意思。-->
<!-- property:java对象的属性名 -->
<!--ofType 属性用来指定集合当中的元素类型。-->
<collection property="stus" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByCollection" resultMap="clazzResultMap">
select c.cid,c.cname,s.sid,s.sname from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
</select>
<!--分步查询第二步:根据cid获取班级信息。-->
<select id="selectByIdStep2" resultType="Clazz">
select cid,cname from t_clazz where cid = #{cid}
</select>
</mapper>
缓存
缓存是一种优化的操作,它可以减少我们的IO操作和查找算法,缓存是针对DQL语句的,即select语句
一级缓存
mybatis的一级缓存是默认开启的,不需要做额外的配置,只要使用同一个sqlSession对象执行同一条sql语句,默认就会走缓存,一级缓存是存在sqlSession对象里的
try {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
SqlSession sqlSession = factory.openSession();
StudentDao studentDao1 = (StudentDao) sqlSession.getMapper(StudentDao.class);
Student student1 = studentDao1.selectById(1);
System.out.println(student1);
StudentDao studentDao2 = sqlSession.getMapper(StudentDao.class);
Student student2 = studentDao2.selectById(1);
System.out.println(student2);
} catch (IOException e) {
e.printStackTrace();
}
什么时候不走缓存
1.SqlSession对象不是同一个,肯定不走缓存,因为一级缓存就是保存SqlSession对象中的
2.查询条件不一样,肯定也不走缓存,因为缓存中根本就没有它的缓存
什么时候一级缓存失效
1.执行了sqlSession的clearCache()方法,这是手动清空缓存
2.执行了INSERT,UPDATE,DELETE语句,不管你是操作哪张表的,都会清空一级缓存
为什么这样设计?
为了保证数据的准确性
try {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
SqlSession sqlSession = factory.openSession();
StudentDao studentDao1 = (StudentDao) sqlSession.getMapper(StudentDao.class);
Student student1 = studentDao1.selectById(1);
System.out.println(student1);
//手动清空缓存
sqlSession.clearCache();
StudentDao studentDao2 = sqlSession.getMapper(StudentDao.class);
Student student2 = studentDao2.selectById(1);
System.out.println(student2);
} catch (IOException e) {
e.printStackTrace();
}
二级缓存
使用二级缓存的步骤:
1.在需要使用二级缓存的sqlMapper.xml文件中添加配置:<cache />
2.使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口
3.SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才能用。
当SqlSession对象关闭后,之前存在一级缓存中的数据就会放到二级缓存中
<!--
默认情况下,二级缓存机制是开启的。
只需要在对应的SqlMapper.xml文件中添加以下标签。用来表示“我”使用二级缓存
<cache />
-->
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("config/mybatis01.xml"));
SqlSession sqlSession1 = factory.openSession();
StudentDao studentDao1 = sqlSession1.getMapper(StudentDao.class);
SqlSession sqlSession2 = factory.openSession();
StudentDao studentDao2 = sqlSession2.getMapper(StudentDao.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存)
Student student1 = studentDao1.selectById(1);
System.out.println(student1);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession2是一级缓存)
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存中
sqlSession1.close();
Student student2 = studentDao2.selectById(1);
System.out.println(student2);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存中
// sqlSession1.close();
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存中
sqlSession2.close();
从二级缓存中取数据的前提是一级缓存已经关闭了,一级缓存的优先级比较高,先从一级缓存中找
二级缓存的失效
只要两次查询之间出现了增删改操作,二级缓存就会失效。【一级缓存也会失效】
二级缓存的相关配置
下面的这些都是cache标签的属性:
缓存就是往里面放查询出来的数据,这些数据是有向后顺序等属性的,为了不存储不必要的信息,所以它里面有驱逐算法
1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。
a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其
实还有⼀种淘汰算法LFU,最不常⽤。)
b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。
c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
2. flushInterval:
a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀
直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。(二级缓存一刷新缓存就被清空了,即失效了)
3. readOnly:
a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能
会存在安全问题。
b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安
全。
4. size:
a. 设置⼆级缓存中最多可存储的java对象数量。默认值1024。
Mybatis集成EhCache
步骤:
1.引入mybatis-ehcache依赖
2.写ehcace.xml配置文件
3.在sqlMapper.xml文件中添加<cache type="...ehcache" />
4.写测试代码
这个不需要死记硬背,用到的时候看文档一步一步来就行了
集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常⻅,按照以下步骤操
作,就可以完成集成:
第⼀步:引⼊mybatis整合ehcache的依赖。
!--mybatis集成ehcache的组件-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<!--ehcache需要slf4j的⽇志组件,log4j不好使-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
<scope>test</scope>
</dependency>
第⼆步:在类的根路径下新建echcache.xml⽂件,并提供以下配置信息。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘存储:将缓存中暂时不使⽤的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
<diskStore path="e:/ehcache"/>
<!--defaultCache:默认的管理策略-->
<!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有
效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
<!--maxElementsInMemory:在内存中缓存的element的最⼤数⽬-->
<!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
<!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-
->
<!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多⻓时间没有被访问就会失
效。只对eternal为false的有效。默认值0,表示⼀直可以访问-->
<!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。
只对eternal为false的有效。默认值0,表示⼀直可以访问-->
<!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
<!--FIFO:first in first out (先进先出)-->
<!--LFU:Less Frequently Used (最少使⽤).意思是⼀直以来最少被使⽤的。缓存的元
素有⼀个hit 属性,hit 值最⼩的将会被清出缓存-->
<!--LRU:Least Recently Used(最近最少使⽤). (ehcache 默认值).缓存的元素有⼀
个时间戳,当缓存容量满了,⽽⼜需要腾出地⽅来缓存新的元素的时候,那么现有缓存元素中时间戳
离当前时间最远的元素将被清出缓存-->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDis
k="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStor
eEvictionPolicy="LRU"/>
</ehcache>
第三步:修改SqlMapper.xml⽂件中的标签,添加type属性。
<!--集成EhCache组件-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/
第四步:编写测试程序使⽤。
@Test
public void testSelectById2() throws Exception{
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().b
uild(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
}
逆向工程
所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类
等。
要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。
思考:使⽤这个插件的话,需要给这个插件配置哪些信息?
pojo类名、包名以及⽣成位置。
SqlMapper.xml⽂件名以及⽣成位置。
Mapper接⼝名以及⽣成位置。
连接数据库的信息。
指定哪些表参与逆向⼯程。
......
逆向工程就是一个组件,它可以根据数据库表来给你生成Java代码和xml配置文件
使用逆向工程的思路:
1.为了让它从数据库中获取信息生成Java代码,我们需要告诉它数据库的连接信息
知道要操作哪张表后它可以生成代码了,但是它并不知道生成后要把代码放到哪里,所以我们需要明确的告诉它
2.类和xml文件生成到哪一个位置也需要告诉它
你想把信息告诉插件,告诉java代码只有通过配置文件
这个也不需要死记硬背,用的时候按照文档操作即可
第一步:在pom中添加逆向⼯程插件
<!--定制构建过程-->
<build>
<!--可配置多个插件-->
<plugins>
<!--其中的⼀个插件:mybatis逆向⼯程插件-->
<plugin>
<!--插件的GAV坐标-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.1</version>
<!--允许覆盖-->
<configuration>
<overwrite>true</overwrite>
</configuration>
<!--插件的依赖-->
<dependencies>
<!--mysql驱动依赖(插件的依赖)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
允许覆盖:逆向工程逆向生成java类以后,如果再次运行这个组件,那么它会把原先文件内容全部清空,然后再写入,如果不配的话,生成的东西是追加的方式
mysql驱动依赖:要通过逆向工程反向生成Java程序是需要连接数据库的,连接数据库需要使用Mysql的依赖
第三步:配置generatorConfig.xml
该⽂件名必须叫做:generatorConfig.xml
该⽂件必须放在类的根路径下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//E
N"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime有两个值:
MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。
MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!--防⽌⽣成重复代码-->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersP
lugin"/>
<!--注释信息的生成-->
<commentGenerator>
<!--是否去掉⽣成⽇期-->
<property name="suppressDate" value="true"/>
<!--是否去除注释-->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--连接数据库信息-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/powerno
de"
userId="root"
password="root">
</jdbcConnection>
<!-- ⽣成pojo包名和位置 -->
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
<!--是否开启⼦包-->
<property name="enableSubPackages" value="true"/>
<!--是否去除字段名的前后空⽩-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- ⽣成SQL映射⽂件的包名和位置 -->
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
<!--是否开启⼦包-->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- ⽣成Mapper接⼝的包名和位置 -->
<javaClientGenerator
type="xmlMapper"
targetPackage="com.powernode.mybatis.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 表名和对应的实体类名-->
<table tableName="t_car" domainObjectName="Car"/>
</context>
</generatorConfiguration>
第四步:运⾏插件
通过generate命令运行插件