ROM框架发展:JDBC -> DbUtils(QueryRunner) -> JdbcTemplate -> Hibernate -> MyBatis
前三个只能称为简单的工具,sql语句都写在java源代码中,硬编码高耦合。
Hibernate : 全自动全映射ORM(Object Relation Mapping)框架,全部都封装好了,亦在消除sql
缺点:sql语句不能定制优化,由框架自动编写(学习HQL可以自己编写,但学习成本高)。查询一个字段把所有字段都映射了,性能不好。
MyBatis:半自动轻量级框架,把sql编写,放到配置文件中,其他操作框架封装好,sql与java编码分离。sql语句交给程序员编写,不失去灵活性。
创建数据库对应的Bean实体类 Emp.java
@Data
public class Emp implements Serializable {
private Integer id;
private String lastName; // 注意这个和数据库字段名不一样
private String gender; // 也可用char
private String email;
}
创建全局MyBatis配置文件,数据源环境信息,事务管理器信息…(类路径下) mybatis-config.xml
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/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="9527"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="empMapper.xml"/>
mappers>
configuration>
创建sql映射xml文件 ,配置每一个sql和结果的封装规则(类路径下)empMapper.xml
<mapper namespace="com.sutong.dao.EmpMapping">
<select id="selectEmp" resultType="com.sutong.bean.Emp">
select `id`, `last_name` as lastName, `gender`, `email` from t_emp where id = #{id}
select>
mapper>
获取执行sql的对象,去执行sql语句( SqlSession实例 - 这一个对象就代表和数据库的一次会话,用完关闭,和Connection一样都是线程不安全的,每次使用都应该去获取新的对象)
public class HelloMyBatis01 {
@Test
public void test01() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 第一个参数是sql语句的唯一标识符!!!
// (就是select标签里面的id,但多个文件id可能冲突,所以需要加上命名空间),第二个的执行sql需要的参数
Emp emp = sqlSession.selectOne("com.sutong.dao.EmpMapping.selectEmp", 1);
System.out.println(emp);
} finally {
sqlSession.close(); // 始终都要关闭
}
}
}
⭐接口式编程,我们Dao层写的接口可以直接和MyBatis映射文件绑定!MyBatis自动为接口创建代理对象,去执行增删改查
namespace必须指定为接口的全类名select标签的id属性是要绑定接口方法的名public interface EmpMapping {
Emp getEmpById(Integer id);
}
<mapper namespace="com.sutong.dao.EmpMapping">
<select id="getEmpById" resultType="com.sutong.bean.Emp">
select `id`, `last_name` as lastName, `gender`, `email` from t_emp where id = #{id}
select>
mapper>
测试:
//先获取SqlSession实例
try {
EmpMapping mapper = sqlSession.getMapper(EmpMapping.class); // 获取接口实现类,
Emp emp = mapper.getEmpById(1); // 实际调用接口代理的方法,就不用写上面一大堆sql标识了,还有类型检查!
System.out.println(emp);
} finally {
sqlSession.close();
}
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
注意:配置的时候要按照上面这个顺序配置!!
MyBatis的xml配置文件约束(语法),(IDEA自带)
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
类路径下创建 dbconfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=9527
使用:
<properties resource="dbconfig.properties"/>
<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>
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。里面很多设置。!!
mapUnderscoreToCamelCase :是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。这样就可以不用查询的时候在sql语句中用as取别名了。默认值是falsejdbcTypeForNull 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。默认是OTHERlazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。默认是fasleaggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。默认false(3.4.1前默认是true)cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认truelocalCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。原则:即使的默认的,我们确认开启的设置都要显示的配置出来!
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<typeAlias type="com.sutong.bean.Emp"/>
<package name="com.bean"/>
typeAliases>
使用:
<select id="getEmpById" resultType="emp">
select * from t_emp where id = #{id}
select>
批量区别名问题:当多包下有相同的类时,都取默认别名,则MyBatis就会报错,这时可以在重名类上面使用
@Alias注解,给个别类单独起个别名@Alias("emp01"),就不冲突了。
MyBatis为我们起好了一些别名:
_int <–> 映射的类型:int (基本类型就是前面加下划线)integer <–> Integer (基本类型的包装类都是首字母小写)string <–> String
类型处理器:在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都用类型处理器将获取到的值以合适的方式转换成 Java 类型。
Mybatis 3.4.5后添加Java8的新日期API支持
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjNJPdD2-1662964083312)(MyBatis.assets/image-20220123122933733.png)]](https://1000bd.com/contentImg/2023/11/05/005034423.png)
…
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
后面讲!!
将 SQL 映射应用于多种数据库之中。记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
<environment id="test">
<transactionManager type="..."/>
<dataSource type="...">dataSource>
environment>
environments>
可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle" />
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
databaseIdProvider>
在sql映射文件select标签上加上databaseId属性,值为上面的别名
<select id="getEmpById" resultType="emp" databaseId="mysql">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
<select id="getEmpById" resultType="emp" databaseId="oracle">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
1.引用配置文件
将sql映射注册到全局配置中。
<mappers>
<mapper resource="empMapper.xml"/>
mappers>
2.引用(注册)接口
⭐mapper 还有一个属性class :引用(注册)接口
<mappers>
<mapper class="com.sutong.dao.EmpMapping"/>
mappers>
如果有sql映射文件,映射文件名必须和接口同名,并且放在一个目录下
IDEA中不会把java包下的.java以外的文件放到classes里面。可以在resources目录下,创建一个和java包下与dao层同名的包resources.com.sutong.dao.EmpMapper.xml,编译后会自动合并的
// 接口main.java.com.sutong.dao.EmpMapping.java
public interface EmpMapping {
Emp getEmpById(Integer id);
}
<mapper namespace="com.sutong.dao.EmpMapping">
<select id="getEmpById" resultType="emp" databaseId="mysql">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
mapper>
如果没有映射文件,所有的sql语句都是利用注解写在接口上(这种还不如xml,如果改还要动源码,而且sql长了就麻烦了)
public interface EmpMapping {
// Update,Delete等注解都有...
@Select("select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}")
Emp getEmpById(Integer id);
}
3.批量注册
<mappers>
<package name="com.sutong.dao"/>
mappers>
总结:比较重要的复杂的Dao接口来写sql映射文件,不重要的简单的Dao接口为了开发快速可以使用注解!
cache – 该命名空间的缓存配置。cache-ref – 引用其它命名空间的缓存配置。resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。sql – 可被其它语句引用的可重用语句块。insert – 映射插入语句。update – 映射更新语句。delete – 映射删除语句。select – 映射查询语句。接口:
public interface EmpMapper {
Emp getEmpById(Integer id);
void addEmp(Emp emp);
void updateEmp(Emp emp);
void deleteEmp(Integer id);
}
映射文件:
<mapper namespace="com.sutong.dao.EmpMapper">
<select id="getEmpById" resultType="com.sutong.bean.Emp" databaseId="mysql">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
<insert id="addEmp" parameterType="com.sutong.bean.Emp">
insert t_emp(`last_name`, `gender`, `email`) values(#{lastName}, #{gender}, #{email})
insert>
<update id="updateEmp">
update t_emp
set `last_name` = #{lastName}, `gender` = #{gender}, `email` = #{email}
where `id` = #{id}
update>
<delete id="deleteEmp">
delete from t_emp where `id` = #{id}
delete>
mapper>
测试:
// 先获取sqlSessionFactory...
// openSession()方法可以传个boolean值,代表是否自动提交,默认不自动提交!!
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // 获取接口实现类
Emp emp = mapper.getEmpById(1); // 查
mapper.addEmp(new Emp(null, "sutong", "1", "sotong@qq.com")); // 增
mapper.updateEmp(new Emp(1, "Tom", "1", "Tom123@qq.com")); // 改
mapper.deleteEmp(4); // 删
sqlSession.commit(); // 手动提交!!
} finally {
sqlSession.close();
}
Mybati允许增删改直接定义 Integer,Long,Boolean,void 类型的返回值,会帮我们自动封装好!!
例如:
Long addEmp(Emp emp);映射文件中不用写关于返回值的配置!
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert t_emp(`last_name`, `gender`, `email`) values(#{lastName}, #{gender}, #{email})
insert>
测试:
Emp e = new Emp(null, "haha", "0", "haha@qq.com");
mapper.addEmp(e); // 增
System.out.println(e.getId()); // id就有值了
Oracle不支持自增,Oracle使用序列来模拟自增,insert标签里面需要使用一个selectKey标签…
1.单个参数,MyBatis不会做特殊处理
#{参数名} 取出(参数名可以随便写,因为就一个参数!!)
当一个参数是Collection类型(List,Set)或者是数组类型就会特殊处理了
Collection类型的key:collection
#{collection[0]},使用#{param1}不行List类型的key:list
#{list[0]}数组类型:array
#{array[0]}单个参数加了
@Param注解也会特殊处理
2.多个参数,特殊处理。
// 接口方法
Emp getEmpByIdAndLastName(Integer id, String lastName);
<select id="getEmpByIdAndLastName" resultType="emp">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id} and `last_name` = #{lastName}
select>
// 测试
Emp emp = mapper.getEmpByIdAndLastName(1, "Tom"); // 报错
上面这样会报错!因为多个参数会被封装为一个Map,规则:key就是param1...paramN(或者参数的索引也可以),value就是传入的参数值
<select id="getEmpByIdAndLastName" resultType="emp">
select `id`, `last_name`, `gender`, `email`
from t_emp where id = #{param1} and `last_name` = #{param2}
select>
3.命名参数:明确封装时Map的key,接口方法参数使用注解@Param
Emp getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
<select id="getEmpByIdAndLastName" resultType="emp">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id} and `last_name` = #{lastName}
select>
4.如果多个参数正好是业务逻辑的数据模型,直接传入pojo就行了
#{属性名} 就可以取出pojo的属性值
5.如果不是业务路径数据模型,我们可以直接传入Map
接口方法:Emp getEmpByMap(Map
#{key}直接取map中的value就行
6.不是业务路径数据模型,而且经常使用,推荐编写一个TO(Transfer Object)数据传输对象,例如Page对象等
两者效果一样的,但有一点区别
#{} 是以预编译的形式,将参数设置到sql语句中,PreparedStatement,?占位符,可以防止SQL注入${} 取出是值直接拼装在sql语句中,Statement,会有安全问题大多数情况下我们取参数值都应该取使用#{}。原生Jdbc不支持占位符的地方,需要sql拼接是时候可以使用$(),
例如分表拆分 select * from ${year}_salary where xxx,排序select * from t_user oder by ${f_name} ${order}等。
#{} 的丰富用法:
规定参数的一些规则:
javaType,jdbcType,mode(存储过程),numericScale
resultMap,typeHandler,jdbcTypeName,expression(未来准备支持的)
jdbcType(数据库类型)通常在某种情况下需要被设置,我们在数据为null的时候有些数据库可能不能识别mybatis对null的默认处理,例如Oracle(报错),jdbcType OTHER无效的类型。
因为mybatis对所有的null都默认映射为原生的jdbc OTHER 类型,Oracle不能正确。
<insert id="addEmp" parameterType="com.sutong.bean.Emp"> insert t_emp(`last_name`, `gender`, `email`) values(#{lastName}, #{gender}, #{email, jdbcType=NULL}) insert>
- 1
- 2
- 3
- 4
或者改全局设置
这个值默认是OTHER
返回List, Map类型!!
public interface EmpMapper {
List<Emp> getEmpsByLastNameLike(String lastName);
// 返回一条记录的Map,key是列名,value就是该字段对应的值
// 例如:{gender=1, last_name=Tom, id=1, email=Tom123@qq.com}
Map<String, Object> getEmpByIdReturnMap(Integer id);
// 多条记录封装为一个Map,key是这条记录的主键(下面注解可指定),value是该记录封装后的JavaBean
// 例如:{2=Emp{id=2, lastName='Jack', gender='1', email='jack@163.com'},
// 5=Emp{id=5, lastName='haha', gender='0', email='haha@qq.com'}}
@MapKey("id") // 告诉mybatisMap的封装这个map的时候那个属性作为map的key
Map<Integer, Emp> getEmpsByLastNameReturnMap(String lastName);
}
<mapper namespace="com.sutong.dao.EmpMapper">
<select id="getEmpsByLastNameLike" resultType="com.sutong.bean.Emp">
select * from t_emp where `last_name` like #{lastNamr}
select>
<select id="getEmpByIdReturnMap" resultType="map">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
<select id="getEmpsByLastNameReturnMap" resultType="com.sutong.bean.Emp" >
select * from t_emp where `last_name` like #{lastName}
select>
mapper>
⭐resultMap 可以自定义映射规则(1.as起别名 2.设置setting 3.使用resultMap)
public interface EmpMapperPlus {
Emp getEmpById(Integer id);
}
映射文件:
<mapper namespace="com.sutong.dao.EmpMapperPlus">
<resultMap id="MyEmp" type="com.sutong.bean.Emp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
resultMap>
<select id="getEmpById" resultMap="MyEmp">
select `id`, `last_name`, `gender`, `email` from t_emp where id = #{id}
select>
mapper>
resultMap 更强大的功能:关联查询
例如:查询每个员工Emp对应的部门Dept信息:
public class Dept {
private Integer id;
private String deptName;
}
public class Emp {
private Integer id;
private String lastName;
private String gender;
private String email;
private Dept dept;
}
// Dao接口方法
public interface EmpMapperPlus {
Emp getEmpAndDept(Integer id);
}
映射文件:
<mapper namespace="com.sutong.dao.EmpMapperPlus">
<resultMap id="MyDifEmp" type="com.sutong.bean.Emp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<result column="d_id" property="dept.id"/>
<result column="dept_name" property="dept.deptName"/>
<association property="dept" javaType="com.sutong.bean.Dept">
<id column="d_id" property="id"/>
<result column="dept_name" property="deptName"/>
association>
resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp">
select
e.id, e.last_name, e.gender, e.email, e.d_id, d.dept_name
from
t_emp e
join
t_dept d
on
e.d_id = d.id
where
e.id = #{id}
select>
mapper>
association标签还能分步查询:类似子查询!!组合已有的方法完成复杂功能
<mapper>
<resultMap id="MyEmpStep" type="com.sutong.bean.Emp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<association property="dept" select="com.sutong.dao.DeptMapper.getDeptById" column="d_id"/>
resultMap>
<select id="getEmpByIdStep" resultMap="MyEmpStep">
select `id`, `last_name`, `gender`, `email`, `d_id` from t_emp where id = #{id}
select>
mapper>
前提是必须Dept的Dao层得有getDeptById这个方法和对应的映射文件,这个在实际中基本都有的!!
在分步查询过程中,如果需要多列值传入过去,怎么办呢?则需要将多列的值封装到Map中传递,
column="{key1=column1, key2=column2}"(=换成:也行)
分布查询还能实现 延迟加载/按需加载/懒加载!!
每次查询Emp对象的时候,Dept都跟着一起查出来了
延迟加载:Dept信息在我们使用的时候再去查询!!只需要在分步查询的基础上加上两个配置就能完成
全局配置:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
collection标签定义关联集合封装规则!!!
例如:查询部门的时候把部门下的所有员工都查出来!!
public class Dept {
private Integer id;
private String deptName;
private List<Emp> emps;
}
// 接口方法:
public interface DeptMapper {
Dept getDeptByIdPlus(Integer id);
Dept getDeptByIdStep(Integer id);
}
1.关联查询的映射文件(嵌套结果集的方式):
<mapper>
<resultMap id="MyDept" type="com.sutong.bean.Dept">
<id column="d_id" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="emps" ofType="com.sutong.bean.Emp">
<id column="e_id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
collection>
resultMap>
<select id="getDeptByIdPlus" resultMap="MyDept">
select
d.id as d_id, d.dept_name, e.id as e_id, e.last_name, e.gender, e.email
from
t_dept d
left join
t_emp e
on
d.id = e.d_id
where
d.id = #{id}
select>
mapper>
2.分布查询的映射文件:(只要上面那两个设置没关闭是有延迟查询的)
先在EmpMapperPlus.xml里面定义一个使用d_id查询员工返回List 的方法
<select id="getEmpsByDeptId" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email`, `d_id` from t_emp where d_id = #{deptId}
select>
封装到Dept的List集合中
<mapper>
<resultMap id="MyDeptStep" type="com.sutong.bean.Dept">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="emps"
select="com.sutong.dao.EmpMapperPlus.getEmpsByDeptId"
column="id"/>
resultMap>
<select id="getDeptByIdStep" resultMap="MyDeptStep">
select id, dept_name from t_dept where id = #{id}
select>
mapper>
collection和association标签里还有个属性fetchType="lazy"表示延迟加载 ,如果这条查询不需要延迟则可设置为eager
discriminator:鉴别器。可以根据鉴别器根据某列的值改变封装行为。(了解即可)
例如:查Emp,如果查出的是女生就把部分信息查询出来,否则不查询。
下面利用分步查询的基础上加了个鉴别器!!
<resultMap id="MyEmpDis" type="com.sutong.bean.Emp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<discriminator javaType="string" column="gender">
<case value="0" resultType="com.sutong.bean.Emp">
<association property="dept" select="com.sutong.dao.DeptMapper.getDeptById" column="d_id"/>
case>
<case value="1" resultType="com.sutong.bean.Emp"/>
discriminator>
resultMap>
例如:查询员工,要求如果携带了id字段和lastName字段则按照这两个查询,两者携带那一个就按哪一个查询。
if其实就是根据条件拼接SQL语句!
<mapper namespace="com.sutong.dao.EmpMapperDynamicSQL">
<select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp
where 1 = 1
<if test="id != null">
and id = #{id}
if>
<if test="lastName != null and lastName != ''">
and last_name = #{lastName}
if>
select>
mapper>
id不传可能会导致语SQL法错误,解决方案:
1.where后面写个1=1,后面所有条件都加上and(看上面)
2.使用where标签将所有的查询条件包括(MyBatis推荐),MyBatis会帮我们第一个多出来的and或者or
<select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp
<where>
<if test="id != null">
id = #{id}
if>
<if test="lastName != null and lastName != ''">
and last_name = #{lastName}
if>
where>
select>
还能这样写:(OGNL表达式甚至还能调用静态方法使用
@,可看官方文档)<if test="email != null and email.trim() != ''"> and email = #{email} if> /* gender是字符串可以直接和数组比较,会自动转换*/ <if test="gender == 0 or gender == 1"> and gender = #{gender} if>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以用trim标签解决后面多出的and或者or!!(了解)
<select id="getEmpsByConditionIf" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="id != null">
id = #{id} and
if>
<if test="lastName != null and lastName != ''">
last_name = #{lastName}
if>
trim>
select>
类似Java中的swtich-case,只会选一个!!
<select id="..." resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp
<where>
<choose>
<when test="id != null">
id = #{id}
when>
<when test="lastName != null">
last_name = #{lastName}
when>
<otherwise>
1 = 1
otherwise>
choose>
where>
select>
带那一列的值就更新那一列(当然这个set也能用trim标签替换)
<update id="updateEmp">
update t_emp
<set>
<if test="lastName != null">
last_name = #{lastName},
if>
<if test="gender == 0 or gender = 1">
gender = #{gender},
if>
<if test="email != null">
email = #{email}
if>
set>
where id = #{id}
update>
例如:查询指定id的员工,可多个!
public interface EmpMapperDynamicSQL {
// 批量查询
List<Emp> getEmpsByConditionForEach(@Param("ids") List<Integer> ids);
// 批量插入
void addEmps(@Param("emps") List<Emp> emps);
}
批量查询:
<select id="getEmpsByConditionForEach" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp where `id` in
<foreach collection="ids" item="item_id" separator="," open="(" close=")">
#{item_id}
foreach>
select>
批量保存:
第一种(推荐)
<insert id="addEmps">
insert into t_emp(`last_name`, `gender`, `email`, `d_id`) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
foreach>
insert>
第二种(这种还可以用于其他的批量操作!!删除修改都行)
jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into t_emp(`last_name`, `gender`, `email`, `d_id`)
values(#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id});
foreach>
insert>
Oracle不支持values(),(),()语法。支持这种:
#1.多条insert放到begin end之间 begin insert into t_emp(`id`, `last_name`, `gender`, `email`) values(t_emp_seq.nextVal, 'test01', '1', 'test01@qq.com'); insert into t_emp(`id`, `last_name`, `gender`, `email`) values(t_emp_seq.nextVal, 'test02', '0', 'test02@qq.com'); end; #2.利用中间表 insert into t_emp(`id`, `last_name`, `gender`, `email`) select t_emp_seq.nextVal, `last_name`, `gender`, `email` from( select 'test01' as last_name , '1' as gender, 'test01@qq.com' as email from dual union select 'test02' as last_name, '0' as gender, 'test02@qq.com' as email from dual )
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
_parameter :代表整个参数。
如果单个参数则_parameter就代表这个参数。如果多个参数底层封装为Map,则_parameter就代表这个Map
_databaseId:如果配置了DatabaseIdProvider标签(支持多厂商数据库的标签),那么就代表当前厂商数据库的别名。
<select id="getEmps" resultType="com.sutong.bean.Emp">
<if test="_databaseId == 'mysql'">
select * from t_emp
<if test="_parameter != null">
where id = #{id}
if>
if>
<if test="_databaseId == 'oracle'">
select * from emps
if>
select>
在增删改查标签里面,还有个bind标签,可以OGNL表达式的值绑定到一个变量中,方便后来引用这个变量。
<select id="getEmp" resultType="com.sutong.bean.Emp">
<bind name="_lastName" value="'%' + lastName + '%'"/>
select * from e_emp where last_name like #{_lastName}
select>
但还是推荐在传参是时候加上模糊查询规则:
Emp emp = mapper.getEmp("%a%");
sql可以抽取可重用的sql片段,方便后面引用。经常用于抽取查询插入时的很多字段。
<sql id="insertColumn">
`last_name`, `gender`, `email`, `d_id`
sql>
<insert id="addEmp">
insert into t_emp( <include refid="insertColumn">include> )
values (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
insert>
MyBatis包含了一个非常强大的查询缓存特性,它可以非常方便的配置和定制。极大提升了查询效率。默认定义了两级缓存。
一级缓存(本地缓存):与数据库同一次会话期间查询到的数据会放到本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
也叫sqlSession级别的一个Map(每个sqlSession都有一个缓存),是一直存在的,我们没办法关闭。
try {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp01 = mapper.getEmpById(1);
System.out.println(emp01);
Emp emp02 = mapper.getEmpById(1);
System.out.println(emp02);// 打印了两次,但只发送了一次sql
System.out.println(emp01 == emp02); // true
} finally {
sqlSession.close();
}
⭐一级缓存失效情况:
二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制:
一个会话,查询一条数据,这个数据就会放到当前会话的一级缓存中
如果会话关闭,一级缓存中的数据会被保存到二级缓存中(注意会话关闭才会写入)
开启新的会话,查新信息的时候就会参考二级缓存中的内容
sqlSession --> EmpMapper -> Emp
--> DeptMapper -> Dept
不同的namespace查出的数据会放到自己对应的缓存中(Map中)
⭐使用:
1.开启全局二级缓存配置(默认是true)
<setting name="cacheEnabled" value="true"/>
2.取每个mapper.xml中配置使用二级缓存
<cache eviction="LRU" flushInterval="60000" readOnly="false" size="1024"/>
属性可以不写都使用默认的 <cache>cache>
3.因为安全问题,底层使用序列化和反序列化技术,我们的pojo需要实现序列化接口Serializable
注意:查询的数据都会默认放在一级缓存中,只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中。
和缓存相关的设置/属性:
cacheEnabled 如果设置为false,关闭的是二级缓存select标签都有一个useCache="true"属性,默认true。设置为false则关闭的还是二级缓存flushCache="true"属性,默认true,代表增删改执行完之后都要清除缓存(一级二级缓存都会被清空),其实查询标签里面也要这个属性,但默认是false。sqlSession.clearCache() 只会清除当前sqlSession的一级缓存。localCacheScope 本地缓存作用域(影响的是一级缓存),取值SESSION (默认) | STATEMENT (对相同 SqlSession 的不同查询将不会进行缓存。相当于可以禁用一级缓存)图解(查询缓存的顺序:二级缓存 -> 一级缓存 -> 数据库):
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-janiFRDk-1662964083313)(MyBatis.assets/缓存图解.jpg)]](https://1000bd.com/contentImg/2023/11/05/005034057.jpeg)
MyBatis缓存底层就是个简单是Map,有个Cache接口供第三方拓展(Redis,EHCache)
// EHCache依赖
<dependency>
<groupId>org.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>3.9.9version>
dependency>
// EhCache和MyBatis整合包(即官网已经根据ehcache实现了CaChe接口,自定义缓存)
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
使用:
给每个映射文件里加入cache标签
<cache type="org.mybatis.caches.ehcache.EhcacheCache">cache>
<cache-ref namespace="com.sutong.dao.EmpMapper"/>
需要在类路径下放一个ehcache.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">
<diskStore path="D:\Learning\Java_Practice\Java Frame\ehcache"/>
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
然后开启了二级缓存就可以直接使用了(pojo记得实现序列化接口)
@Test
public void test02() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
Emp e1 = mapper1.getEmpById(1);
System.out.println(e1);
sqlSession1.close(); // 关闭才能放到二级缓存中
Emp e2 = mapper2.getEmpById(1);
System.out.println(e2);
sqlSession2.close(); // 可以看到上面我们配置的文件夹里面有缓存的文件了
} finally {
//sqlSession.close();
}
}
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
依赖:
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.6version>
dependency>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value=".." />
<property name="username" value=".." />
<property name="password" value=".." />
<property name="driverClassName" value=".." />
bean>
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
还有一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。
还有个mapperLocations指定sql映射文件的位置(在xml和类不在一个文件夹的时候使用)
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
bean>
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
bean>
需要给接口加实现类,注入到Spring中。
里面获取sqlSession,获取对应的Mapper代理,调用对应的方法就行了。最后注入Spring,后面的Service层就能用了。
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession; }
public User getUser(String userId) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUser(userId);
}
}
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
bean>
或者使用 SqlSessionDaoSupport 抽象类:SqlSessionDaoSupport 用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
// 这样我们的SqlSessionTemplate可以不注入到Spring中,我们可以直接getSqlSession();
// 但他的父类需要注入一个SqlSessionFactory
public User getUser(String userId) {
SqlSession sqlSession = getSqlSession();
return sqlSession.getMapper(UserMapper.class).getUser(userId);
}
}
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
bean>
(这个了解,重要是SSM整合)
上面的我们手动写实现类,下面可以让Spring帮我们!!!⭐
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.sutong.dao"/>
bean>
MyBatis Generator(MBG):专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,以及QBC风格的条件查询。但表连接存储过程等复杂的SQL的定义需要我们手工编写
依赖(当然还需要MySQL驱动和MyBatis依赖):
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.4.0version>
dependency>
详细可以看官网
src/main/resources/generatorConfig.xml
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true" />
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="9527">
jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
javaTypeResolver>
<javaModelGenerator targetPackage="com.sutong.bean" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="mybatis.mapper" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.sutong.dao"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
javaClientGenerator>
<table tableName="t_emp" domainObjectName="Emp">table>
<table tableName="t_dept" domainObjectName="Dept">table>
context>
generatorConfiguration>
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.4.0version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xmlconfigurationFile>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
<executions>
<execution>
<id>Generate MyBatis Artifactsid>
<goals>
<goal>generategoal>
goals>
execution>
executions>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.4.0version>
dependency>
dependencies>
plugin>
plugins>
build>
还要有全局配置文件mybatis-config.xml别忘了
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="dbconfig.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true"/>
settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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="mybatis/mapper/DeptMapper.xml"/>
<mapper resource="mybatis/mapper/EmpMapper.xml"/>
mappers>
configuration>
public class RunMybatisGenerator {
@Test
public void run() throws InterruptedException, SQLException,
IOException, XMLParserException, InvalidConfigurationException {
List<String> warnings = new ArrayList<>();
boolean overwrite = true;
File configFile = new File("src/main/resources/generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}
或者直接右上角Maven的点mybatis-generator命令就可以运行了!!!
它自动把数据库里面的下划线命名法换位Java的驼峰命名法!!
运行后:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BK7zQbZb-1662964083313)(MyBatis.assets/image-20220127143306885.png)]](https://1000bd.com/contentImg/2023/11/05/005034194.png)
@Test
public void test01() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// MyBatis3Simple
try {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
System.out.println(mapper.selectAll()); // 没有自动生成toString()
} finally {
sqlSession.close();
}
// MyBatis3
try {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// xxExample就是封装查询条件的
List<Emp> allEmp = mapper.selectByExample(null); // 查询所有
// (查询员工字母带a的,而且性别的1的) 或者 (Email里面带e的)
EmpExample empExample = new EmpExample(); // 封装查询条件的Example
EmpExample.Criteria c1 = empExample.createCriteria(); // criteria就是拼装条件的
c1.andLastNameLike("%a%").andGenderEqualTo("1");
EmpExample.Criteria c2 = empExample.createCriteria();
c2.andEmailLike("%e%");
empExample.or(c2); // 把或条件拼装上去
List<Emp> empsByExample = mapper.selectByExample(empExample); // 条件查询
} finally {
sqlSession.close();
}
}
MyBatis3生产的是GBC风格的查询!!
⭐重要,对后面的插件开发有用
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NncVCxlz-1662964083314)(MyBatis.assets/分层架构.jpg)]](https://1000bd.com/contentImg/2023/11/05/005034370.jpeg)
1.根据全局配置文件,创建SqlSessionFactory
2.获取SqlSession实例
3.获取接口代理对象 (MapperProxy对象)
4.执行增删改查
①SqlSessionFactory的获取:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSSjAWG2-1662964083314)(MyBatis.assets/创建SqlSessionFactory.jpg)]](https://1000bd.com/contentImg/2023/11/05/005034050.jpeg)
其中的每个
MapperStatement代表一个增删查改标签的详细信息
Configuration包括 全局配置文件+所有mapper映射文件 的详细信息。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);最终得到的是一个
DefalutSqlSessionFactory对象(里面包含了Configuration对象)
总结:把所有配置文件信息解析保存到Configuration对象中,然后返回包含了Configuration的DefalutSqlSessionFactory对象中
②sqlSession的获取:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3MQjxWg-1662964083314)(MyBatis.assets/创建SqlSession.jpg)]](https://1000bd.com/contentImg/2023/11/05/005034296.jpeg)
上图的第七步很重要,是执行所有拦截器的拦截方法的。
SqlSession sqlSession = sqlSessionFactory.openSession();即调用
DefalutSqlSessionFactory方法创建返回一个DefalutSqlSession对象,里面包含了Configuration和Executor对象!!!(Executor对象会在这一步创建)
总结:返回一个DefalutSqlSession 对象,包含了 Configuration和Executor。
③获取接口代理对象

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);返回接口代理对象,里面包含了
DefalutSqlSession对象,所以可以用来执行增删查改。
总结:使用MapperProxyFactory 创建对应代理对象,代理对象包含了DefalutSqlSession对象
④执行增删查改

代理对象 -> DefaultSqlSession -> Executor -> StatementHandler (处理sql语句预编译,设置参数等工作)
-> ParameterHandler (设置预编译参数用的)
-> ResultSetHandler (处理结果集)
(设置参数和处理结果都用到了TypeHandler来做的,处理数据库和Java类型的映射)
查询总结图解:

重要了解四大对象的作用和执行流程就行
根据所有配置文件,初始化出Configuration对象
创建一个DefaultSqlSession 对象,里面包含Configuration和Executor
(根据配置文件中的defaultExecutorType设置创建对应的Executor)
getMapper() ,拿到Mapper接口对应的MapperProxy,里面包含DefaultSqlSession
执行增删改查
DefaultSqlSession中的Excutor的增删改查StatementHandler对象(同时创建ParameterHandler和RestSetHandler)StatementHandler预编译以及利用ParameterHandler设置sql的参数StatementHandler的增删查改方法RestSetHandler封装结果四大对象创建的时候都会有:interceptorChain.plugAll(xxxx) !!!
四大对象创建不是直接返回的而是先调用interceptorChain.plugAll(xxxx) 方法
而plugAll()方法里面是拿到所有的Interceptor(即拦截器,我们需要实现的接口),然后调用每个拦截器的plugin()方法,target = interceptor.plugin(target) ,执行完所有拦截器,后返回target 包装后的对象。
插件机制,我们我们可以使用插件为目标对象创建一个代理对象:AOP
我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行方法
MyFirstPlugin.java
package com.sutong.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
// Intercepts注解完成插件的签名(告诉MyBatis当前插件拦截那个对象的那个方法),里面是Signature数组,
// Signature里面有type属性是要拦截对象(四大对象之一),method属性是要拦截该对象的那个方法,args属性是指定该方法是参数列表
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyFirstPlugin implements Interceptor {
/**
* intercept() - 拦截目标对象目标方法的执行
* @param invocation 目标方法
* @return 返回执行后的返回值
*/
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin - intercept -> " + invocation.getMethod());
Object result = invocation.proceed(); // 执行目标方法,前后可以做一些事
return result;
}
/**
* plugin() - 包装目标对象 (包装:为目标对象插件代理对象)
* 四大个对象的创建都会调用这个方法,只有是我们要拦截的对象才会创建代理对象!否则直接返回
* @param target 目标对象
* @return 返回当前target插件的动态代理
*/
public Object plugin(Object target) {
System.out.println("MyFirstPlugin - plugin -> 将要包装的对象" + target);
Object wrap = Plugin.wrap(target, this); // Mybatis为了我们使用方便,包装了Proxy生成代理对象
return wrap;
}
/**
* setProperties() - 将插件注册时的 property 属性设置进来
* @param properties 插件的配置信息
*/
public void setProperties(Properties properties) {
System.out.println("插件的配置信息:" + properties);
}
}
mybatis-config.xml
<plugins>
<plugin interceptor="com.sutong.interceptor.MyFirstPlugin">
<property name="username" value="sutong"/>
<property name="password" value="666"/>
plugin>
plugins>
如果多个插件拦截同一个对象的同一个方法,则:
plugin()则和注册插件的顺序有关!有几个插件包装几次!!多层代理… (正序)
intercept() 由于的多层代理,一进来是先进入最后一个包装的代理,执行其intercept方法,然后进入第二层代理执行方法…(反序)
创建插件动态代理的时候,是按照插件配置顺序创建代理对象,执行目标方法的时候按照逆向顺序执行的。
动态改变一下sql运行的参数!!
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyFirstPlugin implements Interceptor {
// 偷梁换柱
public Object intercept(Invocation invocation) throws Throwable {
// 动态改变一下sql运行的参数:以前查1号员工,我们实际从数据库查2号员工!!偷梁换柱
// 拿到StatementHandler -> ParameterHandler -> parameterObject值
Object target = invocation.getTarget(); // 我们在拦截的对象
MetaObject metaObject = SystemMetaObject.forObject(target); // 拿到target的元数据
//metaObject.getValue("parameterHandler.parameterObject"); // 获取sql语句用的参数
metaObject.setValue("parameterHandler.parameterObject", 2); // 修改sql的参数
// 执行目标方法
Object result = invocation.proceed();
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {}
}
测试:
@Test
public void test01() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
System.out.println(mapper.getEmpById(1)); // 则这里返回的是2号员工的信息!!!!
} finally {
sqlSession.close();
}
}
PageHelper分页插件的使用(支持多种数据库)
1.依赖:
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.0version>
dependency>
2.配置拦截器
在MyBatis中配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="param1" value="value1"/>
plugin>
plugins>
或 在 Spring 配置文件中配置拦截器插件!!
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
params=value1
helperDialect=mysql
value>
property>
bean>
array>
property>
bean>
3.使用
接口:
// 接口
public interface EmpOther {
List<Emp> getEmps();
}
映射文件:
<mapper namespace="com.sutong.dao.EmpOther">
<select id="getEmps" resultType="com.sutong.bean.Emp">
select `id`, `last_name`, `gender`, `email` from t_emp
select>
mapper>
测试:
@Test
public void test01() throws IOException {
// ...
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 只调用这一个方法进行,第一个是第几页,第二个是每页记录数,返回一个分页的详细信息对象
Page<Object> pageHandler = PageHelper.startPage(2, 2);
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
for (Emp emp : mapper.getEmps()) {
System.out.println(emp);
}
System.out.println("当前页码: " + pageHandler.getPageNum());
System.out.println("总页码: " + pageHandler.getPages());
System.out.println("总记录数: " + pageHandler.getTotal());
System.out.println("每页记录数: " + pageHandler.getPageSize()); // 还有...
} finally {
sqlSession.close();
}
}
// 或者使用PageInfo获得信息,更详细!!PageInfo info = new PageInfo<>(list); 对结果进行包装
// 上面的那几个信息都有,还要 info.isIsFirstPage(),isIsLastPage()...等等!!
// 还可以这样new PageInfo<>(list, navigatePages); navigatePages是个int ,代表连续显示几页!!
public void test02() throws IOException {
// ...
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
PageHelper.startPage(2, 2); // 开始分页
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
List<Emp> list = mapper.getEmps(); // 查询
PageInfo<Emp> info = new PageInfo<Emp>(list, 5); // 告诉插件我们要连续5页!!!
for (Emp emp : list) {
System.out.println(emp);
}
System.out.println("是否是第一页: " + info.isIsFirstPage());
int[] nums = info.getNavigatepageNums(); // 连续显示的页码,就不用我们处理了!!直接返回前端取出
System.out.println(Arrays.toString(nums));
} finally {
sqlSession.close();
}
}
上面的动态SQL使用的foreach严格一样上不算批量处理,那上面只是拼一个很长的SQL一起发送服务器,如果太长了服务器就不能接受了。
有个设置defaultExecutorType 是配置默认的执行器。默认是SIMPLE简单执行器, BATCH 执行器不仅能重用语句还会执行批量更新。
如果在全局配置文件改的话所有是SQL都会变为批量的。我们可以单独设置(在获取sqlSession传入一个参数)。
Mybatis使用
@Test
public void testBatch() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 传入ExecutorType,就可以获得一个批量操作的sqlSession!!!!
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
List<Emp> list = new ArrayList<Emp>();
list.add(new Emp(null, UUID.randomUUID().toString(), "1", "test@qq.com"));
list.add(new Emp(null, UUID.randomUUID().toString(), "0", "test@qq.com"));
list.add(new Emp(null, UUID.randomUUID().toString(), "1", "test@qq.com"));
// ....
// 调用多次添加就行了,时间很短的。
// 非批量的每执行一个增删改查就发送一个SQL到数据库,预编译(n次) -> 设置参数(n次) -> 执行(n次)
// 批量一起发送执行,预编译(1次) -> 设置参数(n次) -> 执行(1次)
for (Emp emp : list) {
mapper.addEmp(emp);
}
sqlSession.commit();
} finally {
sqlSession.close();
}
}
Spring中使用
配置 spring-dao.xml里面:
<bean id="batchSqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" value="sqlSessionFactory"/>
<constructor-arg name="executorType" value="BATCH"/>
bean>
使用:
public class EmpServiceImpl {
private BookMapper bookMapper;
@Autowired
private SqlSession sqlSession;
public void testBatch() {
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // 然后调用里面的增删改查方法就是批量的了
}
}
- 非批量:预编译(n次) -> 设置参数(n次) -> 执行(n次)
- 批量:预编译(1次) -> 设置参数(n次) -> 执行(1次)
存储过程:很多sql语句是集合,编译一次,保存在数据库中的。MyBatis支持对存储过程的调用。
先学习一下存储MySQL中的存储过程:
入参存储过程(入参类似java方法中的形参)
# 参数写法:输入输出类型 参数名称 参数数据类型
# 入参存储过程,例如:根据传来的数字,动态赋值name
create procedure add_name(in target int) #in代表形参,out代表返回值
begin # begin 和 end 之间写我们的逻辑
declare emp_name varchar(11); #定义一个变量
if target = 1 then
set emp_name = '一号员工'; # 赋值需要加个set,否则的判断
else
set emp_name = '其他员工';
end if;
insert into t_emp(`last_name`, `gender`, `email`) values(emp_name, '1', 'test@qq.com');
end
# 调用存储过程:
call add_name(1);
# 删除存储过程:
drop procedure add_name;
出参存储过程(出参类似java方法中的返回值)
# 出参存储过程,例如:统计t_emp表总条数
create procedure count_emp(out count_num int)
begin
# 直接把查询出来的结果赋值给count_num
select count(*) into count_num from t_emp;
end;
# 定义变量前面需要加上@,这样是不显示东西的,需要去查询这个变量
call count_emp(@count_num);
select @count_num;
drop procedure count_emp;
MyBatis中存储过程的调用:
接口:
public interface EmpOther {
void testProcedure(Integer num);
}
映射文件:
<select id="testProcedure" statementType="CALLABLE">
{call add_name( #{num, mode=IN, jdbcType=INTEGER} )}
select>
测试:
@Test
public void test03() throws IOException {
// ...
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
mapper.testProcedure(10); // 调用
} finally {
sqlSession.close();
}
}
学习写枚举的使用
@Test public void testEnumUse() { EmpStatus login = EmpStatus.LOGIN; System.out.println(login.ordinal()); // 枚举索引 System.out.println(login.name()); // 枚举名字 }
- 1
- 2
- 3
- 4
- 5
- 6
测试默认的枚举处理:
public enum EmpStatus {
LOGIN, LOGOUT, REMOVE
}
public class Emp {
private Integer id;
private String lastName;
private String gender;
private String email;
private EmpStatus empStatus; // 员工状态
}
<insert id="addEmp">
insert into t_emp(`last_name`, `gender`, `email`, `empStatus`)
values (#{lastName}, #{gender}, #{email}, #{empStatus})
insert>
测试默认的枚举处理:
try {
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
mapper.addEmp(new Emp(null, "enum", "1", "enum@qq.com", EmpStatus.LOGIN));
sqlSession.commit();
} finally {
sqlSession.close();
}
数据库默认保存的是枚举的名字:LOGIN,即使用EnumTypeHandler处理的!!
我们可以改变让数据库存储枚举索引,使用EnumOrdinalTypeHandler,要设置全局配置文件
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.sutong.bean.EmpStatus"/>
typeHandlers>
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzyWTd6I-1662964083316)(MyBatis.assets/image-20220128190140331.png)]](https://1000bd.com/contentImg/2023/11/05/005034102.png)
上面这样使用的并不多,自定义处理才是使用最多的:
/**
* 希望数据库保存的是状态码,而不是默认的索引或者枚举名!
*/
public enum EmpStatus {
LOGIN(100, "用户登录"), LOGOUT(200, "用户登出"), REMOVE(300, "用户不存在");
private Integer code;
private String msg;
private EmpStatus(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
// 按照状态码返回枚举对象
public static EmpStatus getEmpStatusByCode(Integer code) {
switch (code) {
case 100:
return EmpStatus.LOGIN;
case 200:
return EmpStatus.LOGOUT;
case 300:
return EmpStatus.REMOVE;
default:
return EmpStatus.LOGOUT;
}
}
// 下面还有get/set
}
自定义类型处理器要TypeHandler接口或者继承BaseTypeHandler
// 自定义的枚举类型处理器
public class MyEnumEmpStatusHandler implements TypeHandler<EmpStatus> {
// 定义当前数据如何保存到数据库中
public void setParameter(PreparedStatement preparedStatement, int i,
EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, empStatus.getCode().toString()); // 保存状态码
}
public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
int code = resultSet.getInt(s); // s是数据库字段名
// 根据从数据库拿到的状态码,返回一个枚举对象
return EmpStatus.getEmpStatusByCode(code);
}
public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
int code = resultSet.getInt(i); // i是数据库字段索引
return EmpStatus.getEmpStatusByCode(code);
}
public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
int code = callableStatement.getInt(i); // i是数据库字段索引,是存储过程中拿!!
return EmpStatus.getEmpStatusByCode(code);
}
}
全局配置:
<typeHandlers>
<typeHandler handler="com.sutong.handler.MyEnumEmpStatusHandler"
javaType="com.sutong.bean.EmpStatus"/>
typeHandlers>
保存时:#{empStatus,typeHandler=com.sutong.handler.MyEnumEmpStatusHandler}
查询时:就要使用ResultMap了,自定义,<result column="empStatus" property="empStatus" typeHandler=".."/>
我们应该保证在查询和保存使用的的类型处理器是一样的!!!这样才不会出错
测试:
try {
EmpOther mapper = sqlSession.getMapper(EmpOther.class);
mapper.addEmp(new Emp(null, "myEnum", "0", "myEnum@qq.com", EmpStatus.REMOVE));
sqlSession.commit();
} finally {
sqlSession.close();
}
Emp emp = mapper.getEmpById(14);
System.out.println(emp.getEmpStatus()); // 查询出来的时候会封装为EmpStatus对象!!
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Ua9jwCM-1662964083316)(MyBatis.assets/image-20220128192439653.png)]](https://1000bd.com/contentImg/2023/11/05/005033981.png)