一、特性介绍
1、MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
2、mybatis支持定制化sql、存储过程、以及高级映射
3、支持jdbcTemplate模板,减少模板代码和硬编码
4、MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录
二、MyBatis的缓存
默认打开一级缓存,二级没有打开
1、MyBatis的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就
会从缓存中直接获取,不会从数据库重新访问
1.1、使一级缓存失效的四种情况:
不同的SqlSession对应不同的一级缓存
同一个SqlSession但是查询条件不同
同一个SqlSession两次查询期间执行了任何一次增删改操作
同一个SqlSession两次查询期间手动清空了缓存
2、MyBatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被
缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
2.1二级缓存开启的条件:
a>在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
b>在mapper映射文件中设置标签
c>二级缓存必须在SqlSession关闭或提交之后有效
d>查询的数据所转换的实体类类型必须实现序列化的接口
2.2使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
3、二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
3.1、eviction属性:缓存回收策略,默认的是 LRU。
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
3.2、flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新(清空缓存的意识)
3.3、size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
3.4 、readOnly属性:只读, true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
4、MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
三、开发注意的点
1、传入参数到mapper.xml的sql中
1、@Param注解可以设置参数的可以名,方便在mapper中 #{key} 或 ${key} 取值可以以这个key名
Student selectByNameId(@Param("name") String name, @Param("id") String id);
2、传入的参数是实体类,直接可以#{属性} 、${属性}获取属性值,
3、如上多个参数传入映射SQL,写在映射SQL中都是以rg1, arg0, param1, param2为键的map存储
4、@MapKey注解设置map集合的键,值是每条数据所对应的map集合
@MapKey("id") //设置每条数据的id为键值为整条数据,这样可以多条放入map中,如下是mapper接口方法
Map<String, Object> getAllUserToMap();
2、${} 和 #{} 的区别
1、${}的本质就是字符串拼接,#{}的本质就是占位符赋值
2、${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引
号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,
可以自动添加单引号。
3、#{}可以防止sql注入,${}不可以
4、如下方式即避免了${}的注入危险,也达到了拼接字符串的功能。
select * from t_user where username like "%"#{模糊}"%"
3、mapper接口 和 mapper.xml映射文件注意点
1、mapper接口的全类名和映射文件的命名空间(namespace)保持一致
2、mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
4、动态sql
1、where标签会将将if条件最前方多余的and去掉 ,不能去掉条件最后多余的and
2、choose、when、 otherwise相当于if…else if…else
3、trim用于去掉或添加标签中的内容
常用属性:
prefix:在trim标签中的内容的前面添加某些内容
prefixOverrides:在trim标签中的内容的前面去掉某些内容
suffix:在trim标签中的内容的后面添加某些内容
suffixOverrides:在trim标签中的内容的后面去掉某些内容
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
</if>
<if test="age != '' and age != null">
age = #{age} and
</if>
<if test="sex != '' and sex != null">
sex = #{sex}
</if>
</trim>
</select>
4、foreach标签,separator分隔符,index 是当前迭代的序号
<foreach collection="emps" item="emp" open="(" separator="," close=")" index="index">
#{emp.id}
</foreach>
四、相关运用讲解
1、核心配置文件 mybatis_config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入properties文件-->
<properties resource="jdbc.properties"/>
<settings>
<!--将表列名和属性名称使用驼峰命名法-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--lazyLoadingEnabled 延迟加载的全局开关,默认false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。
否则,每个延迟加载属性会按需加载,默认false-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!--设置包下的实体类别名,类名不区分大小写-->
<typeAliases>
<package name="entity.*"/>
</typeAliases>
<plugins>
<!--分页插件-->
<plugin interceptor="com.github.pagehelper.PageHelper"></plugin>
</plugins>
<!--配置数据源环境-->
<environments default="development">
<environment id="development">
<!-- transactionManager:设置事务管理方式
属性: type="JDBC|MANAGED"
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
MANAGED:被管理,例如由Spring管理事务 -->
<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>
<!--以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致-->
<package name="mapper"/>
<!--<mapper resource="mapper/StudentMapper.xml"/>-->
</mappers>
</configuration>
2、查询结果集中含有实体对象 或 集合 或者 分步查询
2.1、DeptAndEmpMapper 接口
package mapper;
import entity.Dept;
import entity.Emp;
import org.apache.ibatis.annotations.Param;
public interface DeptAndEmpMapper {
Dept getAllEmpByDept(@Param("deptId") Integer deptId);
Emp getEmpDeptByEmpId(@Param("empId") Integer empId);
Emp getEmpDeptByTowStep(@Param("empId") Integer empId);
Dept getDeptById(@Param("deptId") Integer deptId);
}
2.1核心配置文件中的映射文件 mapper.xml
<?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="mapper.DeptAndEmpMapper">
<!--如下是单个实体类列名对应属性名映射-->
<resultMap id="empMap" type="entity.Emp">
<id column="id" property="id"/>
<result column="name" property="name"/>
</resultMap>
<resultMap id="deptMap" type="entity.Dept">
<id property="deptId" column="dept_id" />
<result property="deptName" column="dept_name"/>
</resultMap>
<resultMap id="empDeptMap" type="entity.Emp">
<id column="id" property="id"/>
<result column="name" property="name"/>
<association property="dept" resultMap="deptMap"/>
</resultMap>
<!-- 2、一对一关系,对象中含有对象-->
<select id="getEmpDeptByEmpId" parameterType="java.lang.Integer" resultMap="empDeptMap">
select * from emp e left join dept d on d.dept_id = e.dept_id where e. id = #{empId}
</select>
<resultMap id="empByDeptMap" type="entity.Dept">
<id property="deptId" column="dept_id" />
<result property="deptName" column="dept_name"/>
<!--对象中含有集合 ofType:指定返回值类型 或者 resultMap-->
<collection property="empList" resultMap="empMap" />
</resultMap>
<!-- 3、一对多关系,对象中含有集合-->
<select id="getAllEmpByDept" parameterType="java.lang.Integer" resultMap="empByDeptMap">
select * from dept d left join emp e on d.dept_id = e.dept_id where d.dept_id = #{deptId}
</select>
<resultMap id="empDeptByTowStepMap" type="entity.Emp">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- ①select指定查询的唯一sql id , column指定查询入参,fetchType:eager立即加载 lazy懒加载-->
<association property="dept" fetchType="lazy" select="mapper.DeptAndEmpMapper.getDeptById" column="dept_id"/>
</resultMap>
<!--1、分两次查询sql-->
<select id="getEmpDeptByTowStep" parameterType="java.lang.Integer" resultMap="empDeptByTowStepMap">
select * from emp e where e.id = #{empId}
</select>
<!-- ②第二次查询-->
<select id="getDeptById" parameterType="java.lang.Integer" resultMap="deptMap">
select * from dept e where e.dept_id = #{deptId}
</select>
</mapper>
2.3、测试
public class DeptAndEmpTest {
private SqlSession getSqlSession() throws IOException {
//读取核心配置文件
InputStream resource = Resources.getResourceAsStream("mybatis_config.xml");
//创建SQLSession工厂类
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resource);
//创建会话,并设置自动提交事务 true
return sqlSessionFactory.openSession(true);
}
@Test//3、一对多关系,对象中含有集合-
public void test01() throws IOException {
SqlSession sqlSession = getSqlSession();
DeptAndEmpMapper mapper = sqlSession.getMapper(DeptAndEmpMapper.class);
Dept allEmpByDept = mapper.getAllEmpByDept(1);
System.out.println(allEmpByDept);
}
@Test//2、一对一关系,对象中含有对象
public void test02() throws IOException {
SqlSession sqlSession = getSqlSession();
DeptAndEmpMapper mapper = sqlSession.getMapper(DeptAndEmpMapper.class);
Emp emp = mapper.getEmpDeptByEmpId(1);
System.out.println(emp);
}
@Test//1、分两次查询sql
public void test03() throws IOException {
SqlSession sqlSession = getSqlSession();
DeptAndEmpMapper mapper = sqlSession.getMapper(DeptAndEmpMapper.class);
Emp emp = mapper.getEmpDeptByTowStep(1);
System.out.println(emp.getName());
}
}
3、MyBatis获取参数值的方式
3.1、StudentMapper 接口
package mapper;
import entity.Student;
import org.apache.ibatis.annotations.Param;
public interface StudentMapper {
Student selectStudent(String id);
Student selectByName(String name);
//封装参数以param值为key存入map
Student selectByNameId(@Param("name") String name, @Param("id") String id);
Student selectByStudent(Student student);
int selectById(String id);
int insertAreaDict(@Param("name") String name);
}
3.2、StudentMapper.xml
<?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)保持一致
mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致-->
<mapper namespace="mapper.StudentMapper">
<resultMap id="studentMap" type="entity.Student">
<id property="sId" column="s_id"/>
<result property="sName" column="s_name"/>
<result property="sBirth" column="s_birth"/>
<result property="sSex" column="s_sex"/>
</resultMap>
<!-- ${}的本质就是字符串拼接,#{}的本质就是占位符赋值
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引
号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,
可以自动添加单引号-->
<select id="selectStudent" resultType="entity.Student" parameterType="java.lang.String">
select * from student where s_id = #{id}
</select>
<select id="selectById" resultType="java.lang.Integer">
select count(*) from student where s_id = #{id}
</select>
<!--${} 3.5版本需要value或者-->
<select id="selectByName" resultMap="studentMap" >
select * from student where s_name = '${value}'
</select>
<!--@Param设置参数键名 -->
<select id="selectByNameId" resultMap="studentMap" >
select * from student where s_name = #{name} and s_id = #{id}
<!--多个变量会默认放入map中以arg1, arg0, param1, param2为键-->
<!--select * from student where s_name = #{param1} and s_id = #{param2} -->
</select>
<!--参数是实体类型可以直接取属性值-->
<select id="selectByStudent" resultMap="studentMap" parameterType="entity.Student">
select * from student where s_name = #{SName} and s_id = #{sId}
</select>
<!-- useGeneratedKeys:设置使用自增的主键, keyProperty:将主键值设置在该属性上 -->
<insert id="insertAreaDict" useGeneratedKeys="true" keyProperty="UUID">
INSERT INTO mooc_area_dict_t (UUID,show_name) VALUES (null, #{name});
</insert>
</mapper>
3.3 测试
public class MybatisStartTest {
@Test//#{}占位符
public void test01() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
int count = studentMapper.selectById("01");
System.out.println(count);
sqlSession.close();
}
@Test
public void test02() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudent("01");
System.out.println(student);
sqlSession.close();
}
@Test//${} 3.5版本需要value或者
public void test03() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectByName("王菊");
System.out.println(student);
sqlSession.close();
}
@Test//@Param设置参数键名
public void test04() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectByNameId("王菊","08");
System.out.println(student);
sqlSession.close();
}
@Test//参数是实体类型可以直接取属性值
public void test05() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student user = new Student();
user.setSName("王菊");
user.setSId("08");
Student student = studentMapper.selectByStudent(user);
System.out.println(student);
sqlSession.close();
}
@Test//插入数据,自动生成主键id
public void test06() throws IOException {
SqlSession sqlSession = getSqlSession();
//通过代理模式创建StudentMapper的代理实现类
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
studentMapper.insertAreaDict("上传");
sqlSession.close();
}
private SqlSession getSqlSession() throws IOException {
//读取核心配置文件
InputStream resource = Resources.getResourceAsStream("mybatis_config.xml");
//创建SQLSession工厂类
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resource);
//创建会话,并设置自动提交事务 true
return sqlSessionFactory.openSession(true);
}
}
五、分页插件的使用
①添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
②配置分页插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
1、在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数
2、在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的展示的页码数
@Test
public void testPage(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询功能之前开启分页功能
Page<Object> page = PageHelper.startPage(5, 4);
List<Emp> list = mapper.selectByExample(null);
//查询功能之后可以获取分页相关的所有数据
PageInfo<Emp> pageInfo = new PageInfo<>(list, 5);
list.forEach(System.out::println);
System.out.println(pageInfo);
}
3、分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页展示的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]