Mybatis中文官方文档:https://mybatis.org/mybatis-3/zh/index.html
HelloWorld:
流程:
-
1.根据xml配置文件(全局配置文件:有数据源一些运行环境信息)创建一个SqlSessionFactory对象
-
2.sql映射文件,配置每一个sql,以及SQL的封装规则等,
-
3.将sql映射文件注册在全局配置文件中
-
4.写代码
1).根据全局配置文件得到SqlSessionFactory
2).使用sqlSession工厂,获取sqlSession对象使用他来执行增删改查
一个sqlSession代表着一次和数据库的一次会话,用完要关闭
3).使用sql的唯一标识来告诉Mybatis执行那个sql,sql都是保存在
1.加入Mybatis对应的jar包:(我的是Maven)
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
<scope>runtime</scope>
</dependency>;https://mybatis.org/mybatis-3/zh/index.html
2.创建MyBatis的配置文件,并创建好test类来创建sqlSessionFactory
@Test
public void test() throws IOException {
String resource = "MyBatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
//2.获取sqlSession实例,能直接执行已经映射的sql语句
/*sqlSession有两个参数,第一个为sql的唯一标识符,也即是id
第二个为执行sql的参数
*/
SqlSession sqlSession = sqlSessionFactory.openSession();
Employee o = sqlSession.selectOne("com.Mybatis.EmployeeMapper.selectEMP", 1);
System.out.println(o);
sqlSession.close();
}
注意:Mybatis的配置文件中的mapperr节点中一定要对应sql的映射文件
MyBatis接口式编程:
MyBatis为了方便使用者,提供了一种接口式编程:
步骤:
1.创建对应的接口:
package COM.dao;
import COM.bean.Employee;
public interface EmployeeMapperDap {
public Employee getEmpById(Integer id);
}
2.在MyBatis的sql映射文件调整代码,调整名称空间,和标签的id(对应接口的方法):
<?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.dao.EmployeeMapperDap">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型
#{id},从传递过来的参数中取出id值
-->
<select id="getEmpById" resultType="COM.bean.Employee">
select id,last_name lastname,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
3.使用即可:
@Test
public void test2() throws IOException {
//1.获取sqlSessionFactory
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(resourceAsStream);
//2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperDap mapper = sqlSession.getMapper(EmployeeMapperDap.class);
try {
Employee empById = mapper.getEmpById(1);
System.out.println(mapper.getClass());
System.out.println(empById);
}finally {
sqlSession.close();
}
}
}
引入外部配置文件:
<!-- 使用properties来引入外部文件-->
<properties resource="db.properties"></properties>
运行时行为设置:
补充,驼峰命名法的规则是通过bean的getter setter方法名来判定的.
typealiases(别名处理器)
本身package指定的包,应该是类名小写,但是别名不区分大小写
实际代码:
<typeAliases>
<!-- typeAliases:为某个Java类型起别名
type:指定要起别名的类型全类名,默认别名就是类名小写
alias:指定新的别名
-->
<!--单一起别名-->
<!--<typeAlias type="COM.bean.Employee" alias="emp"></typeAlias>-->
<!--批量起别名:
package:可以为某个包下的所有类批量起别名
name:指定报名(为当前包以及下面所有的后带包的每一个都器一个默认别名(类名小写))
批量起别名的情况下,可以使用@alias()为一个类单独且别名,为了防止类名冲突
-->
<package name="COM.bean"/>
</typeAliases>
<?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.dao.EmployeeMapperDap">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型
#{id},从传递过来的参数中取出id值
-->
<select id="getEmpById" resultType="emp">
select id,last_name,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
typeHandlers(类型处理器):
plugins(插件):
environment(环境):
<!-- 4.environments环境,mybatis可以配置多种环境,通过default填入字节点environment的id来设置当前的环境
environment:配置一个具体的环境信息
必须具备的两个子节点:id表示当前环境的唯一标识
transactionManager:事务管理器
type:事务管理器的类型
在mybatis有两种JDBC和MANAGED
自定义事务管理器:实现TransactionFactory接口,type指定为全类名
DataSource 数据源 :
type数据源类型:支持UNPOOLED(非连接池)|POOLED(连接池)|JNDI
自定义数据源:实现DataSourceFactory接口,type是全类名
-->
<environments default="development">
<!-- <environment id="test">
<transactionManager type=""></transactionManager>
<dataSource type=""></dataSource>
</environment>-->
<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>
databaseProvider:
1.步骤:
在你的MyBatis的配置文件中配置databseIdProvider:
<!--databaseIdProvider:支持多数据库厂商
type="DB_VENDOR" VendorDatabaseIdProvider
作用就是得到是数据库厂商的标识,mybatis就能根据数据库厂商标识执行不同sql
-->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<!--MySQL表示mysql数据库的表示,value表示获取标识后起别名-->
</databaseIdProvider>
<!--将我们写好的sql映射文件一定要注册到全局配置文件中-->
在mapper文件中加入属性 databaseId 告知运行环境
<select id="getEmployee" resultType="employee" databaseId="mysql">
select * from tbl_employee where id=#{id}
</select>
6.mappers:将sql映射注册到全局配置中
Class属性:
<mappers>
<!-- mapper:注册一个sql映射
resource:引用类路径下的sql映射文件
url:引用网路径或磁盘路径下的文件
注册接口:
class:两种方法
1.引用(注册)接口:映射文件必须和接口同名,并且放在与接口同一目录下:
2.没有sql映射文件,所有的sql都是利用注解写在接口上
推荐:比较重要的Dao接口我们来写sql映射文件
不重要的:简单的Dao接口为了开发可以使用快速接口
-->
<mapper resource="MyBatisMapper/EmployeeMapper.xml"/>
<!-- <mapper class="COM.dao.daoMappersAnnotation.EmployeeMapperDaoAnnotation"></mapper>-->
<!-- 批量注册-->
<!-- <package name="COM.dao"/>-->
</mappers>
相当于,创建了一个不需要mapper映射文件的或者说需要一个简单的快速接口,需要时使用class属性:
第一种.注解在class填入接口路径,mapper和mapper映射文件在一个包内
第二种.使用注解即可,
Packae标签:
接口必须和mapper在同一目录下,便于访问mapper映射的sql文件
MyBatis映射文件
基本步骤:
1.在你的接口上定义增删改查的方法:
package COM.dao;
import COM.bean.Employee;
public interface EmployeeMapperDao {
public Employee getEmpById(Integer id);
public void addEmpById(Employee employee);
public void updateEmpById(Employee employee);
public long deleteEmpById(Integer id);
}
2.在你的sqkl映射文件加入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.dao.EmployeeMapperDao">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型
#{id},从传递过来的参数中取出id值
-->
<select id="getEmpById" resultType="employee" databaseId="mysql">
select id,last_name,email,gender from tbl_employee where id = #{id}
</select>
<insert id="addEmpById" databaseId="mysql" parameterType="employee">
insert into tbl_employee(last_name,gender,email)
values (#{lastname},#{gender},#{email})
</insert>
<delete id="deleteEmpById" databaseId="mysql" >
delete from tbl_employee where id=#{id}
</delete>
<update id="updateEmpById" databaseId="mysql" parameterType="employee">
update tbl_employee set last_name=#{lastname},email=#{email},gender=#{gender} where id=#{id}
</update>
</mapper>
3.然后就可以使用了
/*
*
* 1.MyBatis允许增删改查直接(在接口上面)定义一下类型返回值:integer,long:返回执行了多少行
boolean:返回执行是否成功
* 2.我们需要手动提交数据
*
* */
@Test
public void test() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee employee=new Employee(1,"a","1","c@qq.ciom");
mapper.deleteEmpById(1);
sqlSession.commit();
sqlSession.close();
}
}
需要注意的是mybatis不会自动提交所以在你的接口实现类完成操作后,要让sqlsession提交:
2.mybatis会自动提方法包装返回值,只需要在你的接口定义方法的时候定义好返回值即可,返回值可以是 long integer Boolean:
获取自增主键的值:
通过两个属性来获取:
<!-- parameterType表示参数类型,一般可以省略
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGeneratedKeys
parameterType="true",使用自增主键获取主键值策略
keyProperty="id":指定对应的主键属性,也就是mybatis获取到主键值以后,把这个值封装给javaBean的那个属性
-->
<insert id="addEmpById" useGeneratedKeys="true" keyProperty="id" databaseId="mysql" parameterType="employee">
insert into tbl_employee(last_name,gender,email)
values (#{lastname},#{gender},#{email})
</insert>
会通过,mybatis也是利用statement.getGeneratedKeys方法来,传入到(当前的填入的id)bean对象中
获取非自增主键的值:
MyBatis参数处理:
单个参数:
mybatis不会做特殊处理
#{参数名},取出参数值
红色框子填什么都可以,因为你只传入了一个参数
多个参数:
mybatis会做特殊处理,多个参数会被封装成一个map
key:param1,param2,param3.或者参数索引也可以
value:传入参数值
出现异常
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
- 操作:
方法:
public Employee getEmpByIdAndLastName(Integer id,String name);
取值:#{id} #{lastname}
对应的mapper
<!--俩个参数 public Employee getEmpByIdAndLastName(Integer id,String name);
-->
<select id="getEmpByIdAndLastName" databaseId="mysql">
select * from tbl_employee where id=#{id} and last_name=#{last_name}
</select>
命名参数:明确指定封装参数时map的key
多个参数会被封装成一个map
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
注意:@Param(“id”)这里的id 对应的是#{id}中的id
Pojo&&Map&&To:
Pojo
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo
#{属性名}:取出出入的pojo的属性值
例子:
对应接口
public boolean updateEmpById(Employee employee);
对应mapper
<update id="updateEmpById" databaseId="mysql" parameterType="employee">
update tbl_employee set last_name=#{lastname},email=#{email},gender=#{gender} where id=#{id}
</update>
Map
如果多个参数不是我们业务模型的数据,没有对应的pojo,为了方便,我们也可以传入map
例子:
对应接口
public Employee getEmpByIdAndLastMap(Map<String ,Object> map);
方法
@Test
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Map a=new HashMap();
a.put("id", 1);
a.put("lastname", "c");
Employee empByIdAndLastMap = mapper.getEmpByIdAndLastMap(a);
System.out.println(empByIdAndLastMap);
sqlSession.close();
}
对应映射:
<!-- 参数处理pojo map-->
<select id="getEmpByIdAndLastMap" resultType="employee" databaseId="mysql">
select * from tbl_employee where id=#{id} and last_name=#{lastname}
</select>
To
如果多个参数不是业务模型中的数据,但是经常要使用,推荐写一个TO(Transfer Object)数据传输对象
Page{
int index;
int size;
}
思考:
public Employee GetEmp(@Param("id") Integer id,String lastName);在映射文件中怎么赋值
取值:id-->#{id/param1} last_name-->#{param2}
public Employee GetEmp( Integer id,Employee emp);在映射文件中怎么赋值
取值:id-->#{id/param1} last_name-->#{param2.lastname}
public Employee GetEmp( Integer id,@Param("em") Employee emp);在映射文件中怎么赋值
取值:id-->#{id/param1} last_name-->#{param2.lastname/em.lastname}
特别注意,如果是Collection类型(list,set)类型或者数组。会特殊处理,会把list或者数组封装进map中
如果key是Collection(collection)
是List(list)
是数组(array)
public Employee GetEmp(List<Integer> ids);在映射文件中怎么赋值
取值:id-->#{list[0]}
参数值的获取:
测试
select * from tbl_employee where id=${id} and last_name=#{lastname}
结果
select * from tbl_employee where id=1 and last_name=?
#{}和${}的区别:
- #{}:以预编译的形式,将参数设置到sql语句中:PreparedStatement,防止sql注入
- ${}:取出的值直接拼装在sql语句中:会有安全问题
- 大多情况下,我们都应该#{}来取值
- 当原生jdbc不支持占位符的时候我们可以使用${}进行取值
比如分表:按照年份分表来拆分
select * from ${year}_salary where xxx;
select * from ${year}_salary order by ${f_name};
例如:
现在map中加入键值对(表名)
在mapepr的select标签使用${}:
#{}其他用法:
jdbcType通常需要在某种特定的条件下被设置
在我们数据为null的时候,有些数据库不能识别mybatis对null的默认处理,比如Oracle(报错)
JdbcType OTHER:无效类型,因为mybatis对所有的null所映射的是原生JDBC的 OTHER oracle不接受
原因:在全局配置汇总。jdbcTypeForNULL=OTHER Oracle不接受
解决办法:
1.设置jdbcType
<select id="getEmpByIdAndLastMap" resultType="employee" databaseId="mysql">
select * from tbl_employee where id=${id,jdbcType=NULL} and last_name=#{lastname}
</select>
2.修改全局配置 :
select元素:
返回值为list的时候:
你的mapper的resultType不需要填入list,只需要填入基本类型即可:
- public List getEmpsByLastName(String lastName);
返回值为Map的时候:
1.返回一条记录的map:
key就是列名,值就是对应的值:
返回值为一条map,MyBatis自动将返回的employee的列名和值封装进map中
接口名:
// 返回一条记录的map:key就是列名,值就是对应的值:
public Map<String,Object> getEmpByIdReturnMap(Integer id);
对应mapper:
<!-- 返回值为一条map,MyBatis自动将返回的employee的列名和值封装进map中-->
<select id="getEmpByIdReturnMap" resultType="map" >
select * from tbl_employee where id=#{id}
</select>
运行结果:
2.返回多条记录的map
多条记录封装一个map,Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
接口:
// 返回多条记录的map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean
@MapKey("id")
// 告诉mybaits封装这个map的时候那个属性作为主键
public Map<Integer,Employee> getEmpsByLastNameReturnMap(String LastName);
对应的mapper:
<!-- 返回多条记录的map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean-->
<select id="getEmpsByLastNameReturnMap" resultType="employee" >
select * from tbl_employee where last_name like #{lastname}
</select>
:
运行结果
自定义结果映射:
<!-- 自定义某个javaBean的封装规则
type:自定义规则的Java类型
id:唯一id方便引用
-->
<resultMap id="MyEmp" type="COM.bean.Employee">
<id column="id" property="id"></id>
<!--指定主键列的封装规则:
id定义主键会底层优化
column:指定一列对应property
property:指定对应的javaBean属性
-->
<!-- 定义普通列封装规则-->
<result column="last_name" property="lastname"></result>
<!-- 其他不指定的列会自动封装-->
</resultMap>
<!-- resultMap:自定义结果集映射规则-->
<select id="getEmpById" resultMap="MyEmp" >
select * from tbl_employee where id = #{id}
</select>
关联查询:
配置你的外键,以及对应的类:
配置好的表
Employee类:
对应的department类
配置你的map
<resultMap id="MyEmpAndDept" type="employee">
<id column="id" property="id"></id>
<!--指定主键列的封装规则:
id定义主键会底层优化
column:指定一列对应property
property:指定对应的javaBean属性
-->
<!--定义普通列封装规则-->
<!--联合查询:级联属性封装结果来-->
注意:这里的dept是javabean Empployee的对应department的属性名
<result column="last_name" property="lastname"></result>
<result column="gender" property="gender"></result>
<result column="d_id" property="dept.id"></result>
<result column="dept_name" property="dept.departmentName"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="MyEmpAndDept" >
select a.id,a.gender,a.last_name,a.email,a.d_id,b.dept_name from tbl_employee a,tbl_depart b where a.d_id=b.id and a.id=#{id}
</select>
association:
<!--association:-->
<resultMap id="MyDifEmp" type="employee">
<!-- association可以指定联合的javaBean对象
dept:指定那个对象是联合的对象
javaType:指定这个属性指定的类型(不可以省略)
-->
<result column="last_name" property="lastname"></result>
<result column="gender" property="gender"></result>
<association property="dept" javaType="department">
<!-- 这里的id指的是department里的id-->
<id column="d_id" property="id"></id>
<id column="dept_name" property="departmentName"></id>
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp" >
select a.id,a.gender,a.last_name,a.email,a.d_id,b.dept_name from tbl_employee a,tbl_depart b where a.d_id=b.id and a.id=#{id}
</select>
分布查询:
步骤:
1.构建思路:
使用association进行分布查询:
1.先按员工id查询员工信息
2.根据查询员工信息中的d_id值去部门表查出部门信息
3.部门设置到员工中
2.创建department表对应查询的mapper和接口:
然后在另一个mapper配置association:
注意:图中select:一定要对应接口的方法名,路径写全
新加的mapper要加入的Mybatis.
延迟加载:
延迟加载:
Employee==>Dept
我们每次查询Employee对象的时候,都将一起查询出来
要求:部门信息只在我们使用的时候在查询
方法:
在分布查询的基础上加上两个配置
lazyLoadingEnabled:我们需要什么加载什么
aggressiveLoading:我们需要任何一个或多个 直接全部加载
<settings>
<!-- 延迟加载 -->
<!-- lazyLoadingEnabled:要什么给什么
aggressiveLazyLoading:要一个全都给
-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
collection定义封装规则:
问题:当我们需要通过一个department来给我们employee的集合,我们该怎么做?
思考:我们需要通过部门表的depid 来做文章:
sql语句:select b.id did,b.dept_name dn,a.id eid,a.last_name ln,a.gender gender
from tbl_depart b
left join tbl_employee a
on b.id=a.id
where b.id=#{id}
在你的deparment类中创建list
对应的mapper:
collection的分布查询:(继上一个方法)
大体与association的意思差不多:
department与employee的方法接口:
public Department getDeptByIdStep(Integer id);
public List<Employee> getDeptByIdStep(Integer id);
对应的mapper:
拓展:collection和association中的分布查询,当我们需要传入多个参数时:
当分布查询,需要传入多列的时候
将多列的值分装成map传值
column="{key1=value1,key1=value1}"
注意:key一定要对应#{这里的值} 例如:#{a} key就是a
fetchType=lazy:表示使用延迟加载
fetchType=eager:表示使用立即加载
对应的mapper:
<resultMap id="MyDeptByIdStep" type="department">
<id column="id" property="id"></id>
<result column="dept_name" property="departmentName"></result>
<collection property="employees" select="COM.dao.EmployeeMapperPlus.getDeptByIdStep"
column="{xid=id}" fetchType="lazy"
>
</collection>
</resultMap>
discriminator
<!--discriminator:
鉴别器:mybatis可以使用discriminator判断某列的值,根据某列的值改变封装行为
封装employee:
如果查出是女生:就把部门信息查询出来,否则不查询
如果是男生,就把lastName赋给email
-->
<resultMap id="myEmpByDiscriminator" type="employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastname"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<!--
column:指定判定的列名
javaType:列值对应的java类型
-->
<discriminator javaType="string" column="gender">
<!-- 女生resultType:指定封装的结果类型 resultType:不可缺少-->
<case value="0" resultType="employee">
<association property="dept" select="COM.dao.DepartmentMapper.getDeptName"
column="d_id">
</association>
</case>
<!-- 男生resultType-->
<case value="1">
<id column="id" property="id"></id>
<result column="last_name" property="lastname"></result>
<result column="gender" property="gender"></result>
<result column="last_name" property="email"></result> </case>
</discriminator>
</resultMap>
<select id="getDeptByIdDiscriminator" resultMap="myEmpByDiscriminator" >
select * from tbl_employee where id=#{id}
</select>
动态sql:
- 动态SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作。
- 动态SQL 元素和使用JSTL 或其他类似基于XML 的文本处理器相似。
- MyBatis 采用功能强大的基于OGNL 的表达式来简化操作。
–if
–choose (when, otherwise)
–trim (where, set)
–foreach
if:
小技巧:
查询的时候如果某些条件没带sql拼装会有问题
1.给where后面加上1=1,以后的条件都and xx
2.mybatis使用where标签来将所有的查询条件包括在内,mybatis的where会将sql语句中的or and删除
这个where只会去掉第一个where
trim(自定义字符创截取):
<!--
当sql语句 后面多出and or的时候 ,where不可以解决
Trim(自定义字符串截取)的属性:
prefix="":前缀,trim标签体中是整个字符拼串后的结果
prefix给拼串后trim标签体的整个加一个前缀
prefixOverrides="" 前缀覆盖,表示去掉整个字符串前面多余的字符
suffix="" 后缀 suffix给拼串后trim标签体的整个加一个后缀
suffixOverrides="" 后缀覆盖,表示去掉整个字符串前面多余的字符
-->
<select id="getEmpByConditionTrim" resultType="employee">
select * from tbl_employee
<trim prefix="where" prefixOverrides="" suffix="" suffixOverrides="and">
<if test="id!=null">
id =#{id} and
</if>
<if test="lastname!=null and lastname!=""">
last_name like #{lastname} and
</if>
<if test="email!=null and email.trim()!=""">
email = #{email} and
</if>
<if test="gender==0 or gender==1">
gender = #{gender}
</if>
</trim>
</select>
choose(分支选择)
<select id="getEmpByConditionChoose" resultType="employee">
select * from tbl_employee
<where>
<!-- 如果带了id 查id ...-->
<choose> 相当于switch
<when test="id!=null"> 相当于case
id=#{id}
</when>
<when test="lastname!=null">
last_name like #{lastname}
</when>
<when test="email!=null">
last_name like #{lastname}
</when>
<otherwise> 就是otherwise
gender=0
</otherwise>
</choose>
</where>
</select>
set与if的动态更新:
可以让帮助我们把,去除
<update id="updateEmp">
update tbl_employee
<!-- Set:标签的使用-->
<!-- <set>-->
<!-- <if test="lastname!=null">-->
<!-- last_name=#{lastname},-->
<!-- </if>-->
<!-- <if test="email!=null">-->
<!-- email=#{email},-->
<!-- </if>-->
<!-- <if test="gender!=null">-->
<!-- gender=#{gender},-->
<!-- </if>-->
<!-- </set>-->
<!--Trim-->
<trim prefix="set" suffixOverrides=",">
<if test="lastname!=null">
last_name=#{lastname},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
</trim>
<where>
id=#{id}
</where>
</update>
foreach:
foreach下的批量查询:
首先在你的方法对参数命名 ,以便于取值
//通过foreach的使用,来批量保存
public void addEmpsByForeach(@Param("emps") List<Employee> emps);
然后:
<!--批量保存-->
<!-- 方法一-->
<!-- mysql下批量保存,可以foreach遍历 mysql支持values(),()语法-->
<!-- <insert id="addEmpsByForeach" >-->
<!-- insert into tbl_employee (last_name,gender,email,d_id)-->
<!-- VALUES-->
<!-- <foreach collection="emps" item="emp" separator=",">-->
<!-- (#{emp.lastname},#{emp.gender},#{emp.email},#{emp.dept.id})-->
<!-- </foreach>-->
<!-- </insert>-->
<!--方法二-->
<insert id="addEmpsByForeach" >
<foreach collection="emps" item="emp" separator=";">
insert into tbl_employee (last_name,gender,email,d_id)
VALUES
(#{emp.lastname},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
两个参数( parameter和databaseId):
两个内置参数:
不只是方法转递过来的参数可以被用来判断,取值...
mybatis默认还有两个那内置参数
parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map:_parameter就是代表这个map
databaseId:如果配置了DatabaseProvider的标签
那么_databaseId就有值
-->
<select id="getEmpTestPara" resultType="employee">
<if test="_databaseId='mysql'">
select * from tbl_employee
<if test="_parameter!=null"></if>
where last_name =#{_parameter.lastname} and d_id=#{_parameter.did}
</if>
</select>
传入多个参数
<select id="getEmployeeByInnerParam" resultType="employee">
<if test="_databaseId=='mysql'">
<if test="_parameter!=null">
select * from tbl_employee where id=#{_parameter.param1} and last_name like #{_parameter.param2}
</if>
</if>
</select>
Bind:
注意:
需求:我们模糊查询的时候,不需要在打入%
方法
这样当然可以,因为${}是将sql语句拼起来吗,但是会出现sql注入问题,所以我们选择bind
sql抽取可重复的sql语句:
<select id="getEmpTestBind" resultType="employee">
select
<!--include标签用来引用外部标签-->
<include refid="insertColumn">
</include>
from tbl_employee
<if test="_parameter!=null">
<bind name="_lastname" value="'%'+lastname+'%'"/>
where last_name like #{_lastname}
</if>
</select>
<!--
sql标签:
抽取可重用的sql片段,方便后面引用
1.我们经常使用到这个标签,将要查询的列名,或者插入用的列明抽取出来方便引用
2.include来引用已经抽取到的sql
可以自定义属性,sql可以在内部适用:用法就是 ${} ,#{}不可以使用
例如:
<include refid="insertColumn">
<property name="testColumn" value="depatName"/>
</include>
对应的sql标签:
<sql id="insertColumn" >
* ${testColumn}
</sql>
-->
<sql id="insertColumn" >
*
</sql>
缓存
一级缓存(本地缓存):sqlSession级别的缓存,一级缓存是一直开启的
- 与数据库同一次会话期间查询到的数据会放在本地缓存中,sqlSession级别的缓存,一级缓存是一直开启的
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询。
例子:
/*
* 两级缓存:
* 一级缓存:(本地缓存)sqlSession级别的缓存,一级缓存是一直开启的,sqlSession级别的缓存,一级缓存是一直开启的
* 与数据库同一次会话期间查询到的数据会放在本地缓存中
* 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询。
* 二级缓存:(全局缓存)
* */
@Test
public void testFirstLevelCache(){
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee empById = mapper.getEmpById(1);
System.out.println(empById);
//比如,当你查询了一次,你还需要再次查找一次,在传统的JDBC中,我们需要在监理一次会话等
Employee empById1 = mapper.getEmpById(1);
System.out.println(empById1);
sqlSession.close();
}
注意:这里的运行结果
虽然我们查了两次,但是,我们只用了一个会话,查询语句是一样的,所以我们只用了一次会话
一级缓存失效的情况:
条件:
- 1.sqlSession不同,代表两次不同的会话
@Test
public void testSecondLevelCache(){
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee empById = mapper.getEmpById(1);
System.out.println(empById);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmployeeMapperDao mapper1 = sqlSession2.getMapper(EmployeeMapperDao.class);
Employee empById1 = mapper1.getEmpById(1);
System.out.println(empById==empById1);
}
运行结果:
- 2.sqlSession相同,但查询条件不一样(一级缓存 没有上一次的数据)
@Test
public void testSecondLevelCache(){
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee empById = mapper.getEmpById(1);
System.out.println(empById);
Employee empById1 = mapper.getEmpById(2);
System.out.println(empById==empById1);
}
结果:
- 3.sqlSession相同,但两次查询之间,增加增删改操作
@Test
public void testSecondLevelCache(){
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee empById = mapper.getEmpById(1);
System.out.println(empById);
//3.sqlSession相同,但两次查询之间,增加增删改操作
mapper.addEmpById(new Employee(null,"giaoge", "0", "654@qq.com0"));
System.out.println("add Success");
Employee empById1 = mapper.getEmpById(1);
System.out.println(empById==empById1);
}
- 4.sqlSession相同,但手动清除了缓存
@Test
public void testCacheUnSuccess(){
EmployeeMapperDao mapper = sqlSession.getMapper(EmployeeMapperDao.class);
Employee empById = mapper.getEmpById(1);
System.out.println(empById);
// 4.sqlSession相同,但手动清除了缓存
sqlSession.clearCache();
Employee empById1 = mapper.getEmpById(1);
System.out.println(empById==empById1);
}
理解:说白了就是一个sqlSession的map,根据sql来判别
二级缓存:(全局缓存):基于namespace级别的缓存,一个namespace代表一个二级缓存
工作机制:
1.一个会话:查询一条数据,这个数据就会被放在当前会话的一级缓存中
2.如果会话关闭后,对应的一级缓存就会保存到二级缓存中,新的会话查询信息,就可以参照二级缓存
3.一个namespace对应一个二级缓存 ,不同namespace查出的数据会放在自己的Map
使用:
cache属性:
- eviction=“FIFO”:缓存回收策略
•LRU –最近最少使用的:移除最长时间不被使用的对象。
•FIFO –先进先出:按对象进入缓存的顺序来移除它们。
•SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
•WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
•默认的是LRU。 - flushInterval:刷新间隔,单位毫秒
•默认情况是不设置,也就是不清空 - size:引用数目,正整数
•代表缓存最多可以存储多少个对象,太大容易导致内存溢出 - readOnly:只读,true/false
•true:只读缓存;会将数据的引用交给用户,说白了,速度快,不安全
•false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。 - type:指定自定义的缓存的全类名
/*二级缓存:(全局缓存):
二级缓存:(全局缓存):基于namespace级别的缓存,一个namespace代表一个二级缓存**
工作机制:
1.一个会话:查询一条数据,这个数据就会被放在当前会话的一级缓存中
2.如果会话关闭后,对应的一级缓存就会保存到二级缓存中,新的会话查询信息,就可以参照二级缓存
3.一个namespace对应一个二级缓存 ,不同namespace查出的数据会放在自己的Map
注意:数据会从二级缓存查找的前提。是上一个sqlSession必须关闭
二级缓存生效的前提 ,sqlSession关闭,一级缓存才会将数据提交到二级缓存中
(默认顺序:一级缓存---(这个sqlSession提交或关闭)二级缓存)
使用:
1.开启全局二级缓存配置:在mybatis的全局变量配置 <setting name="cacheEnabled" value="true"/>
2.去mapper.xml中配置使用二级缓存
<cache></cache>
cache标签的属性:
eviction=“FIFO”:缓存回收策略
•LRU –最近最少使用的:移除最长时间不被使用的对象。
•FIFO –先进先出:按对象进入缓存的顺序来移除它们。
•SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
•WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
•默认的是LRU。
flushInterval:刷新间隔,单位毫秒
•默认情况是不设置,也就是不清空
size:引用数目,正整数
•代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly:只读,true/false
•true:只读缓存;会将数据的引用交给用户,说白了,速度快,不安全
•false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
type:指定自定义的缓存的全类名
3.我们的POJO需要实现序列化
*/
@Test
public void SecondLevelCacheTest(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmployeeMapperDao mapper1 = sqlSession1.getMapper(EmployeeMapperDao.class);
EmployeeMapperDao mapper2 = sqlSession2.getMapper(EmployeeMapperDao.class);
Employee empById = mapper1.getEmpById(1);
System.out.println(empById);
sqlSession1.close();
//注意:这里的第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
Employee empById2 = mapper2.getEmpById(1);
System.out.println(empById2);
sqlSession2.close();
System.out.println(empById==empById2);
}
- 和缓存有关的设置/属性:
1).CacheEnabled=true/false: 表示开启/关闭二级缓存 (一级缓存不可以被关闭)
2). useCache=true/false: mapper文件中每一个select标签都拥有这个属性,表示使用二/不使用级缓存
3).flushCache=true: mapper文件中每一个增删改标签都拥有,表示当前这个方法执行完
毕清除缓存(一级二级全都会被清除)
4).sqlSession2.clearCache(); 表示只清除当前session的一级缓存
5).localCacheScope: 本地缓存(一级缓存)作用域:当前会话的所有数据保存在会话缓存中.
属性值:
Session:默认值,当前会话使用缓存
STATEMENT:当前会话没有缓存(手动关闭一级缓存)
注意:每次当你使用增删改操作的时候,都会刷新缓存(一级二级)
个人理解: 一级缓存和二级缓存面对的对象是不一样,一级缓存面对的是同一个sqlSession内同一个namespace的操作,二级缓存则是所有sqlSession的不同的namespace的缓存域,所以当你有新的会话时,优先查找二级缓存的
缓存的原理:
缓存的顺序: 二级缓存>一级缓存》数据库,原因在上面个人理解和图片
MyBatis逆向工程:
MBG官网:http://mybatis.org/generator/
MBG运行
对应的配置文件,
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />-->
<properties resource="db.properties"></properties>
<!-- 数据库连接-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"
userId="root"
password="123456">
</jdbcConnection>
<javaTypeResolver >
<!--java类型解析器,具体看文档-->
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--指定javaBean生成策略-->
<javaModelGenerator targetPackage="Bean" targetProject="D:\Code\SSM_Work\MybatisMBG\src\main\webapp\Java">
<!-- targetPackage:目标包
targetProject:目标工程
-->
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--sql映射文件策略-->
<sqlMapGenerator targetPackage="Mappers"
targetProject="D:\Code\SSM_Work\MybatisMBG\src\main\webapp\Resouorces">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!--指定Mapper接口所在位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="Mappers" targetProject="D:\Code\SSM_Work\MybatisMBG\src\main\webapp\Java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!--指定逆向生成的数据库的表,根据表创建javaBean-->
<table tableName="tbl_employee" domainObjectName="Employee"></table>
<table tableName="tbl_depart" domainObjectName="Department"></table>
</context>
</generatorConfiguration>
对应的java运行代码:
public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File(MBG.class.getClassLoader().getResource("Mbg.xml").getPath());
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);
}
生成成功:
QBC风格的拼装条件查询
代码
@Test
public void testExample() throws Exception {
String Resource="mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(Resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
// 查询条件拼装
// 创建一个criteria
EmployeeExample employeeExample=new EmployeeExample();
EmployeeExample.Criteria criteria = employeeExample.createCriteria();
// 为你的criteria写上上查询条件
criteria.andLastNameLike("%e%");
// criteria.andGenderEqualTo("1");
// criteria.andDIdIsNull();
// 在通过selectByExample查询
List<Employee> employees = mapper.selectByExample(employeeExample);
System.out.println(employees);
}
源码分析:
sqlSessionFactory:
sqlSession:
图中pluginAll方法是用来遍历所有的插件,基于拦截器
getMapper获取接口的代理对象:
查询流程
总结:
总结:
插件:
插件在你的每一个四大对象都会遍历,但他只会根据注解来指定绑定,并返回代理对象,对对象进行封装
- 编写插件,实现interceptor的接口:
package plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
/**
@Intercepts: 表示当前这个类是一个插件
@Signature: 为插件签名,告诉mybaits当前插件用来拦截哪一个对象的哪一个方法
**/
@Intercepts({
@Signature(type = StatementHandler.class,method ="parameterize",args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {
/**
intercept:拦截
拦截目标对象(四大对象)的某一个方法
**/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行目标方法
System.out.println("intercept");
Object proceed = invocation.proceed();
// 返回执行后的返回值
return proceed;
}
//plugin:
// 包装目标对象,包装,为目标对象常见一个代理对象
@Override
public Object plugin(Object target) {
/**
* Mybatis提供Plugin来为target包装成一个代理对象
*/
System.out.println("plugin "+target);
Object wrap = Plugin.wrap(target, this);
System.out.println(wrap.toString());
return wrap;
}
/**
*
* setProperties:
* 将插件注册时的property属性设置进来,通过mybaits配置文件中plugins,配置,方便上面方法提取
*
*/
@Override
public void setProperties(Properties properties) {
System.out.println("properties:"+properties);
}
}
- 使用@intercepts完成插件签名
运行:
}
/**
* 1.插件原理:
* 在四大对象创建的时候
* 1.每个对象出来的时候不是直接返回的,而是
* InterceptorChain.pluginAll(param)
* 2.获取到所有的Interceptor,并通过plugin方法包装返回target对象
* 3.插件机制,我们可以使用插件为目标对象创建一个代理对象,AOP
*
*2.步骤:
* 1.编写插件,实现interceptor的接口
* 2.使用@intercepts完成插件签名
* 3.在你的Mybatis中,注册你的插件
* @throws Exception
*/
@Test
public void testPlugin() throws IOException {
String Resource="mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(Resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectByPrimaryKey(1);
System.out.println(employee);
}
注意拦截器每一个对象都会拦截,只不过在你的@Intercepts下面注明:具体为哪一对象产生动态代理,
运行流程:
PageHelper插件(分页)
中文官网:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
- 导入jar包
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.4</version>
</dependency>
- 配置插件:
<!--pageHelper-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<!-- -->
</plugin>
</plugins>
- 使用
@Test
public void testPageHelper() throws IOException {
String Resource="mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(Resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
/**
PageHelper
Page<Object> objects = PageHelper.startPage(1, 5);
System.out.println("当前页码"+objects.getPageNum());
System.out.println("总记录数"+objects.getTotal());
System.out.println("每页记录数"+objects.getPageSize());
System.out.println("总页码"+objects.getPages());
*/
Page<Object> objects = PageHelper.startPage(1, 5);
List<Employee> emps = mapper.getEmps();
// NavigateFirstPage表示显示最多页码数
PageInfo pageInfo=new PageInfo(emps,5);
System.out.println("当前页码"+pageInfo.getPageNum());
System.out.println("总记录数"+pageInfo.getTotal());
System.out.println("每页记录数"+pageInfo.getPageSize());
System.out.println("总页码"+pageInfo.getPages());
System.out.println("第一页"+pageInfo.getNavigateFirstPage());
System.out.println("是否有下一页"+pageInfo.isHasNextPage());
System.out.println("NavigateFirstPage"+pageInfo.getNavigatepageNums());
/*
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
*/
System.out.println(emps);
}