MyBatis
MyBatis简介
MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作,MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录,MyBatis本是apache的一个开源项目ibatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis (ORM 对象关系映射)
在spring中学到的jdbcTemplate只是一个工具类,只能去写和增删改查相关的功能,和框架是有区别的
Mybatis就是帮助程序员将数据存入数据库中,和从数据库中取数据
传统的jdbc操作,有很多重复代码块,比如:数据取出时的封装,数据库的建立链接等,通过框架可以减少重复代码,提高开发效率
MyBatis是一个半自动化的ORM框架(Object Relationship Mapping 对象关系映射)
半自动的好处:相比hibernet的全自动ORM,MyBatis支持手动写sql,我们在后续优化项目的时候可以通过sql语句优化来提高程序的整体执行效率
通过xml配置,实现了sql语句和核心代码模块的分离
MyBatis的优势
- MyBatis的真正强大在于它的映射语句,这是它的优势所在。由于它的异常强大,映射器的XML文件就显得相对简单,如果拿它跟具有相同功能的JDBC代码进行对比,会立即发现省掉了将近95%的代码,MyBatis为聚焦于SQL而构建,以尽可能地为你减少麻烦
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<!--导入MyBatis坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<!--导入数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--导入日志依赖,需要log4j.properties文件支持-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
MyBatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!--
开启事务,默认开启事务,type:事务类型
-->
<transactionManager type="JDBC"/>
<!--
配置数据源信息
POOLED:采用链接池的形式
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
==与spring中的dataSource数据源不同,驱动的name属性不是driverClassName,而是driver,我们要依照MyBatis官网提供的属性名称来写标签==
MyBatis采用了池的思想,将数据库链接对象
environment 元素体中包含了事务管理和连接池的配置,mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息
读取数据源数据
属性(properties)
- 这些属性可以在外部进行配置,并可以进行动态替换(在配置文件中使用el表达式
${}
来获取到值) - 你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 标签的内部标签property中设置
属性配置文件
db.url=jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-8
db.driver=com.mysql.cj.jdbc.Driver
db.username=root
db.password=root
resource属性:引用外部配置文件,相对路径、绝对路径
<!--用于读取外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
类型别名(typeAliases)
在<configuration>
标签中配置
类型别名可给类起别名,它仅用于 XML 配置,意在降低冗余的全限定类名书写
这样我们在mapper映射文件中就能使用别名了
<!--给实体类定义别名-->
<typeAliases>
<!--给具体的实体定义别名-->
<typeAlias type="com.os467.pojo.Emp" alias="emp"></typeAlias>
<!--给指定包下面的实体定义别名,默认的别名就是类名,而且是不区分大小写的-->
<package name="com.os467"></package>
</typeAliases>
mapper文件中使用别名:
<select id="getEmpList" resultType="emp" resultMap="getEmpListMap">
SqlSessionFactory
SqlSessionFactory的实例是MyBatis应用的核心,SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先配置的Configuration实例来构建出SqlSessionFactory实例
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置,但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流,MyBatis 包含一个名叫 Resources 的工具类(使用的是org.apache.ibatis.io包下的类),它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易
//资源路径
String resource = "org/mybatis/example/mybatis-config.xml";
//读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
MyBatis在底层会通过动态代理为持久层接口创建对应的实现类(接口代理)
测试类:
InputStream resourceAsStream = null;
SqlSession sqlSession = null;
try {
//根据流对象来解析mybatis核心配置文件,读取相关数据
resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建sqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//将sqlSession对象从工厂中取出来
sqlSession = sqlSessionFactory.openSession();
//mybatis在底层会通过动态代理为持久层接口创建对应的实现类
UserDao userDao = sqlSession.getMapper(UserDao.class);
//调用查询用户信息的方法
List<User> userList = userDao.getUserList();
//遍历集合
for (User user : userList) {
System.out.println(user);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭资源
if (resourceAsStream != null){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sqlSession != null){
sqlSession.close();
}
}
配置Mapper文件
mapper文件存放sql资源,在Resource文件下mapper文件资源路径必须和持久层接口的包结构相同
Resource中的包结构以文件夹的形式创建,不能以”.”的方式分隔
可以将映射文件命名为持久层接口名+Mapper的形式
<!--映射一个mapper文件-->
<mappers>
<mapper resource=""></mapper>
</mappers>
引入映射文件约束
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace就是持久层接口的全类名-->
<mapper namespace="">
</mapper>
配置sql语句
详细参考官方文档:https://mybatis.net.cn/sqlmap-xml.html
查询语句
<select>
标签用于编写查询语句
id:必须是接口中的方法名称
resultType属性:需要将映射实体的全类名传过来
resultMap属性:自定义的字段映射规则,如果没有定义的字段就按照默认的映射规则,需要传入resultMap的id值
parameterType属性:如果有参数需要把参数的全类名传过来
<mapper namespace="com.os467.dao.UserDao">
<!--查询所有的用户数据-->
<select id="getUserList">
select * from tb_user
</select>
</mapper>
默认映射规则:
- 当数据库中的字段名和实体类的属性名一致的时候,类型映射器将自动赋值,并返回一个完整的User对象
- 而当有属性名和字段名不一致的时候,自动赋值无法完成,会出现属性值为空的情况
解决方案
1、在sql语句中使用别名
这种方式就不能用*,比较麻烦
<select id="getEmpList" resultType="com.os467.pojo.Emp"> select emp_name as empName from emp
</select>
2、设置映射结果集,自定义映射关
在select标签中添加resultMap属性,引入自定义的映射结果集
以下将字段中的emp_name映射到实例中的empName,没有设置映射关系的就按照默认的映射规则
<select id="getEmpList" resultType="com.os467.pojo.Emp" resultMap="empMap">
select * from emp
</select>
<!--映射结果集-->
<resultMap id="empMap" type="com.os467.pojo.Emp">
<!--匹配主键字段-->
<id column="eid" property="id"></id>
<!--匹配非主键字段,column是字段名,property对应属性名-->
<result column="emp_name" property="empName"></result>
</resultMap>
底层模拟
模拟底层为接口创建动态代理:
public <T>T getProxy(Class<T> aClass){
//根据流对象来解析mybatis核心配置文件,读取相关数据
try {
resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
//创建sqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//将sqlSession对象从工厂中取出来
sqlSession = sqlSessionFactory.openSession();
Object o = Proxy.newProxyInstance(aClass.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(new UserDao() {
public List<User> getUserList() {
return sqlSession.selectList("getUserList");
}
public List<Emp> getEmpList() {
return sqlSession.selectList("getEmpList");
}
}, args);
return invoke;
}
});
return (T)o;
}
模拟SqlSession中getMapper方法底层:
模拟一个mybatis通过反射创建的持久层接口实现类,通过聚合sqlSession读取mapper文件来映射对应的sql并且返回结果
package com.os467.mapper;
import com.os467.pojo.User;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
/**
* 假如这个类就是mybatis底层创建出来的实现类
*/
public class UserMapperImpl implements UserMapper {
private SqlSession sqlSession;
public UserMapperImpl(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
/**
* 查询所有的用户数据
* @return
*/
public List<User> getUserList() {
//读取mapper文件,根据全局标识来映射到对应的sql,底层类似dom4j解析
List<User> userList = sqlSession.selectList("com.os467.mapper.UserMapper.getUserList");
return userList;
}
}
测试:
//创建一个持久层的实现类的实例
UserMapper userMapper = new UserMapperImpl(sqlSession);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
单元测试中的Before和After标签:
可以减少测试方法中的代码量
InputStream resourceAsStream = null;
SqlSession sqlSession = null;
SqlSessionFactory sqlSessionFactory = null;
/**
* @Before 这个注解的作用:会在所有的测试单元执行之前执行
*/
@Before
public void mybatisBefore(){
//根据流对象来解析mybatis核心配置文件,读取相关数据
try {
resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
//创建sqlSessionFactory工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//将sqlSession对象从工厂中取出来
sqlSession = sqlSessionFactory.openSession();
}
/**
* @After 这个注解的作用:会在所有的测试单元执行之后执行
*/
@After
public void mybatisAfter(){
//关闭资源
if (resourceAsStream != null){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sqlSession != null){
try {
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
删除语句
OGNL表达式
在mybatis中可以通过OGNL表达式去取值
OGNL表达式: #{ }
可以取到实体中某一个属性的值,在执行sql的时候可以防止sql注入
<!--根据id来删除员工数据-->
<delete id="deleteEmpByEid" parameterType="com.os467.pojo.Emp">
delete from emp where eid = #{eid}
</delete>
返回值类型为影响记录条数,可以不用写
在开启事务的条件下,增删改语句后还要提交事务
//在关闭资源前,需要提交事务
sqlSession.commit();
mybatis中的批量删除
实现批量删除我们一般使用in函数
,但是mybatis中的OGNL不支持直接连接为字符串,在in函数中使用,我们可以使用${参数名}
放在in函数中使用,但是需要在接口中指定好参数名
使用**@Param(“ids”)注解:** 指定好参数名称,如果想用el表达式
Integer deleteByFids(@Param("fids") String fids);
批量删除语句
<delete id="deleteByFids" parameterType="string">
delete from ssm_fruit
<where>
fid in(${fids})
</where>
</delete>
使用OGNL表达式的批量删除
<select id="selectByIdSet" resultMap="BaseResultMap">
select *
from t_user
WHERE id IN
<foreach collection="array" item="id" index="index" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
修改语句
<!--根据id来修改员工数据-->
<update id="updateEmpByEid" parameterType="com.os467.pojo.Emp">
update emp set
emp_name = #{empName},
age = #{age},
sex = #{sex},
salary = #{salary},
birthday = #{birthday}
where eid = #{eid}
</update>
添加语句
selectKey标签
一些情况下,新增一条数据信息,但其主键(id)是数据库自动在数据库生成(自增),而有些业务逻辑的处理是需要要到这个生成的主键(id)
selectKey 会将 SELECT LAST_INSERT_ID() 函数返回的结果放入到实体中
- keyProperty:对应的实例中的主键的属性名
- keyColumn: 数据库的主键字段名
- useGenerateKeys:插入成功之后否使用JDBC的
getGenereatedKeys方法
获取主键,默认是false - order:
- AFTER 表示 SELECT LAST_INSERT_ID() 在insert执行之后执行,多用与自增主键
- BEFORE 表示 SELECT LAST_INSERT_ID() 在insert执行之前执行,这样的话就拿不到主键了,适合那种主键不是自增的类型
- resultType:返回主键的数据类型
<!--添加员工-->
<insert id="addEmp" parameterType="com.os467.pojo.Emp">
<selectKey resultType="java.lang.Integer" keyColumn="eid" keyProperty="eid" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into emp (
emp_name,
age,
sex,
salary,
birthday
)values (
#{empName},
#{age},
#{sex},
#{salary},
#{birthday}
)
</insert>
模糊查询
OGNL表达式不支持字符串拼接
所以要在传值的时候就把模糊查询的条件给定义好(百分号要提前写好)
OGNL表达式{}中的内容在此处可以是任意的
OGNL表达式使用的是preparedStatement,是有预编译的
empMapper.likeEmpName("%小%");
<!--根据员工姓名来实现模糊查询的功能-->
<select id="likeEmpName" parameterType="java.lang.String" resultMap="getEmpListMap">
select * from emp where emp_name like #{name}
</select>
或者使用el表达式
el表达式支持字符串的拼接
但是el表达式必须在{}内部写value
el表达式不能防止sql注入
el表达式在mybatis中是直接使用statement的,没有预编译,但是模糊查询并没有sql注入这一方面的隐患,因此可以使用
empMapper.likeEmpName("小");
<!--根据员工姓名来实现模糊查询的功能-->
<select id="likeEmpName" parameterType="String" resultMap="getEmpListMap">
select * from emp where emp_name like '%${value}%'
</select>
聚合函数
resultType是需要写的,与增删改语句的影响记录条数不同
<!--求员工的总个数-->
<select id="getEmpCount" resultType="java.lang.Integer">
select count(*) from emp
</select>
parameterType,resultType属性: 如果是java.lang这个包下的可以直接写类名,基本数据类型也可以直接写,不区分大小写
测试单元
package com.os467.test;
import com.os467.mapper.EmpMapper;
import com.os467.mapper.UserMapper;
import com.os467.mapper.UserMapperImpl;
import com.os467.pojo.Emp;
import com.os467.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
public class MybatisTest02 {
InputStream resourceAsStream = null;
SqlSession sqlSession = null;
SqlSessionFactory sqlSessionFactory = null;
EmpMapper empMapper = null;
/**
* @Before 这个注解的作用:会在所有的测试单元执行之前执行
*/
@Before
public void mybatisBefore(){
//根据流对象来解析mybatis核心配置文件,读取相关数据
try {
resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
//创建sqlSessionFactory工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//将sqlSession对象从工厂中取出来
sqlSession = sqlSessionFactory.openSession();
empMapper = sqlSession.getMapper(EmpMapper.class);
}
/**
* @After 这个注解的作用:会在所有的测试单元执行之后执行
*/
@After
public void mybatisAfter(){
//提交事务
sqlSession.commit();
//关闭资源
if (resourceAsStream != null){
try {
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (sqlSession != null){
try {
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 测试查询功能
*/
@Test
public void test01(){
List<Emp> empList = empMapper.getEmpList();
for (Emp emp : empList) {
System.out.println(emp);
}
}
/**
* 测试删除功能
*/
@Test
public void test02(){
Emp emp = new Emp();
emp.setEid(4);
int num = empMapper.deleteEmpByEid(emp);
System.out.println(num==1?"删除成功":"删除失败");
}
/**
* 测试修改功能
*/
@Test
public void test03(){
Emp emp = new Emp();
emp.setEid(1);
emp.setEmpName("张三");
emp.setSalary(6000);
emp.setAge(20);
emp.setSex(2);
emp.setBirthday("2022-07-12");
int num = empMapper.updateEmpByEid(emp);
System.out.println(num==1?"修改成功":"修改失败");
}
/**
* 测试添加功能
*/
@Test
public void test04(){
Emp emp = new Emp();
emp.setEmpName("小刘");
emp.setSalary(10000);
emp.setAge(21);
emp.setSex(2);
emp.setBirthday("2022-07-12");
System.out.println(" 添加之前 "+emp);
int num = empMapper.addEmp(emp);
System.out.println(num==1?"添加成功":"添加失败");
System.out.println(" 添加之后 "+emp);
}
/**
* 测试模糊查询功能
*/
@Test
public void test05(){
List<Emp> empList = empMapper.likeEmpName("小");
for (Emp emp : empList) {
System.out.println(emp);
}
}
/**
* 求总记录条数
*/
@Test
public void test06(){
Integer empCount = empMapper.getEmpCount();
System.out.println("员工总个数为"+empCount);
}
}
Map集合取值
<select id="findEmpByLimit" resultMap="empVo" parameterType="hashmap">
select * from tb_emp order by salary desc limit #{indexStart},#{pageSize}
</select>
可以通过OGNL表达式通过key取到value,或者是el表达式
可以通过.
的方式取到集合内对象的值
==注意在用.
取值的时候,都需要保证key存在才行,否则会报错==
<select id="findGoodsList" resultMap="goodsVo" parameterType="map">
select * from ssm_goods
<where>
<if test="goods.brandId !=-1 ">and brand_id = #{goods.brandId} </if>
<if test="searchTimeRange.upTimeStart !=null and searchTimeRange.upTimeEnd !=null">
and up_time between #{searchTimeRange.upTimeStart} and #{searchTimeRange.upTimeEnd}
</if>
</where>
</select>
动态SQL
动态SQL指的是根据不同的查询条件,生成不同的Sql语句
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
if标签
我们会先给 where 后面加上一个恒成立的条件 1=1
再在后面使用<if></if>
标签设置查询的条件
或者使用<where></where>
标签,将<if></if>
标签放在<where></where>
标签中
<where>
<if test="..">and ...</if>
</where>
test属性:会通过参数中实例的get方法获取到属性,对属性值进行测试,如果属性不为空则生成对应的查询条件
<!--根据条件来查询数据-->
<select id="findUser" parameterType="user" resultType="user">
select * from tb_user where 1 = 1
<if test="username != null">
and username = #{username}
</if>
<if test="password != null">
and password = #{password}
</if>
</select>
choose、when、otherwise标签
类似java的 switch 语句
<!--根据条件来查询数据-->
<select id="findUser" parameterType="user" resultType="user">
select * from tb_user
<where>
<choose>
<when test="username != null">
and username = #{username}
</when>
<when test="password != null">
and password = #{password}
</when>
<otherwise>
and id = 1
</otherwise>
</choose>
</where>
</select>
和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
set标签
用于动态更新语句的类似解决方案叫做 set,set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
<!--根据id来修改员工数据-->
<update id="updateEmpByEid" parameterType="com.os467.pojo.Emp">
update emp
<set>
<if test="empName != null">emp_name = #{empName},</if>
<if test="age != null"> age = #{age},</if>
<if test="sex != null"> sex = #{sex},</if>
<if test="salary != null">salary = #{salary},</if>
<if test="birthday != null">birthday = #{birthday},</if>
</set>
where eid = #{eid}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
与 set 元素等价的自定义 trim 元素
<trim prefix="SET" suffixOverrides=",">
...
</trim>
foreach标签
<!--根据EidS来删除员工数据-->
<delete id="deleteEmpByEidS" parameterType="java.util.List">
delete from emp
where eid in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</delete>
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
script标签
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
详情查看官方文档:https://mybatis.net.cn/dynamic-sql.html
多表联查
涉及业务的实体都要对应一个接口
一对一:账单对用户,一个账单只能对应一个用户
将账单表看成主表
我们一般在主表中聚合一个从表的引用
//聚合从表的引用
private User user;
为聚合的实例赋值
需要在resultMap中配置映射关系
配置查询语句
<!--查询所有的账单数据-->
<select id="findAccountList" resultMap="accountVo">
select * from ts_account a
left join tb_user u
on u.id = a.uid;
</select>
配置映射关系
association标签
通过多表联查找到的主键id字段来为聚合的实例赋值
property属性:就是引用的属性名称
column属性:匹配的主键id
<!--建立起实体属性和表字段的一个对应关系-->
<resultMap id="accountVo" type="account">
<!--匹配主键字段-->
<id column="tid" property="tid"></id>
<!--匹配非主键字段-->
<result column="money" property="money"></result>
<result column="uid" property="uid"></result>
<!--
配置一对一的关系
property 就是引用的属性名称
column 匹配的主键id
-->
<association property="user" column="id">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
</association>
</resultMap>
一对多:用户对账单,一个用户可以对应多个账单
用户表看成主表
//聚合从表的集合引用
private List<Account> accounts;
配置查询语句
<!--查询所有的用户数据-->
<select id="getUserList" resultMap="userVo">
select * from tb_user u
left join ts_account a
on u.id = a.uid
</select>
配置映射关系
collection标签
property属性:集合引用的属性名称
ofType属性:集合的泛型的全类名,有别名可以用别名
<!--建立起实体属性和表字段的一个对应关系-->
<resultMap id="userVo" type="user">
<!--匹配主键字段-->
<id column="id" property="id"></id>
<!--匹配非主键字段-->
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--配置一对多的关系-->
<collection property="accounts" ofType="account">
<!--匹配主键字段-->
<id column="tid" property="tid"></id>
<!--匹配非主键字段-->
<result column="money" property="money"></result>
<result column="uid" property="uid"></result>
</collection>
</resultMap>
多对多:用户和角色,一个用户可以对应多个角色,一个角色也可以对应多个用户
例子:
张三:在家庭中是一个父亲,在工作中,是一个项目经理
项目经理:张三,李四
- 多对多在设计表结构的时候,需要去指定一个中间表,来存放两张表的映射关系
- 中间表中存放的每条记录是 表1的主键id 以及对应的 表2的主键id
- 中间表仅起到过度作用,我们从中间表中去找到两张表的映射关系
- 我们把用户表先看成主表
查询语句
<!--查询所有的用户数据-->
<select id="getUserList" resultMap="userVo">
select u.*,r.*
from tb_user u
/*
先让用户表连接中间表
*/
left join user_role ur
on u.id = ur.uid
/*
将用户表作为主表建立起与中间表的对应关系
再将查询的结果作为新表建立起与角色表的对应关系
*/
left join tb_role r
on r.rid = ur.rid
</select>
配置映射关系
<!--建立起实体属性和表字段的一个对应关系-->
<resultMap id="userVo" type="user">
<!--匹配主键字段-->
<id column="id" property="id"></id>
<!--匹配非主键字段-->
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--配置多对多的关系-->
<collection property="roles" ofType="role">
<!--匹配主键字段-->
<id column="rid" property="rid"></id>
<!--匹配非主键字段-->
<result column="role_name" property="roleName"></result>
</collection>
</resultMap>
延迟加载(懒加载)和立即加载
什么是延迟加载?
真正在使用数据的时候才发起查询,不用的时候不查询,按需加载(懒加载)
什么是立即加载?
不管用不用,一调用方法,马上发起查询,效率会低于延迟加载
- 针对一对多和多对多:通常情况下使用延迟加载
- 针对多对一和一对一:通常情况下使用立即加载
在查询用户数据的时候,有没有必要将账单数据带出来?
没有必要,我们会采用延迟加载的策略
在查询账单数据的时候,有没有必要将用户数据带出来?
有必要,因为在查询账单的时候,需要知道该账单对应的用户是谁,要采用立即加载的策略
配置延迟加载(懒加载)
工程默认情况下采用的是立即加载的策略
设置名:
lazyLoadingEnabled
延迟加载的全局开关,当开启时,所有关联对象都会延迟加载, 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态,默认值就是false
aggressiveLazyLoading
开启时,任一方法的调用都会加载该对象的所有延迟加载属性,否则,每个延迟加载属性会按需加载,默认值false
需要在configuration标签下创建settings标签
<settings>
<!--开启当前工程的延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
一对一中立即加载的配置
查询的时候只查询账户表数据,因为如果使用多表联查是一定会把数据立即查出来的
<select id="findAccountList" resultMap="accountVo">
select * from ts_account
</select>
在association中加入select属性
select属性:提供UserMapper.xml中查询语句的全局标识
column属性:主sql查出来的某一列字段作为参数传递给子sql,提供一对一查询用户表的查询条件
由于全局标识可能会重复,所以我们把名称空间也带上
<!--建立起实体属性和表字段的一个对应关系-->
<resultMap id="accountVo" type="account">
<id column="tid" property="tid"></id>
<result column="money" property="money"></result>
<result column="uid" property="uid"></result>
<association property="user" column="uid" select="com.os467.mapper.UserMapper.findUserById">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
</association>
</resultMap>
UserMapper中新建条件查询sql
<!--根据id来查询对应的用户数据-->
<select id="findUserById" parameterType="java.lang.Integer" resultMap="userVo">
select * from tb_user where id = #{id}
</select>
一对多中按需加载的配置
只有在存放多表数据的属性需要被用到的时候才会去查询并返回结果(延迟加载)
select属性:提供AccountMapper.xml中查询语句的全局标识
column属性:主sql查出来的某一列字段作为参数传递给子sql,提供一对多查询账户表的查询条件
<!--建立起实体属性和表字段的一个对应关系-->
<resultMap id="userVo" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--配置一对多的关系-->
<collection property="accounts" ofType="account" column="id" select="com.os467.mapper.AccountMapper.findAccountByUid">
<!--匹配主键字段-->
<id column="tid" property="tid"></id>
<!--匹配非主键字段-->
<result column="money" property="money"></result>
<result column="uid" property="uid"></result>
</collection>
</resultMap>
AccountMapper中新建条件查询sql
<select id="findAccountByUid" parameterType="java.lang.Integer" resultType="account">
select * from ts_account where uid = #{id}
</select>
注意在使用resultMap的时候不要在两个表间同时配置对方表的select引用,否则会出现栈溢出死循环
★关于mybatis中的缓存
缓存简介
1、什么是缓存
存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2、为什么使用缓存
- 减少和数据库的交互次数,减少系统开销,提高系统效率
3、什么样的数据能使用缓存
- 经常查询并且不经常改变的数据
关于mybatis中的缓存:一级缓存,二级缓存
一级缓存:sqlSession级别的缓存,这个缓存默认是存在的,在我们进行数据查询的时候,mybatis会先去数据库中将对应的数据查出来,查出来之后,会将数据封装成对象,存到缓存域中,下次再发起查询的时候,mybatis会从缓存域中去读取数据,避免了与数据库的多次交互
二级缓存:Mapper(Namespace)级别的缓存,这个缓存可以共享所有的sqlSession,将数据通过序列化的方式存到缓存域中,存放的是数据,不是对象
测试一级缓存
//访问数据库
List<User> userList = mapper.getUserList2();
//从缓存中查
List<User> userList2 = mapper.getUserList2();
//true,内存地址相同,第二次查询是从缓存中取的数据
System.out.println(userList == userList2);
如何让一级缓存失效
1、在调用了清空缓存的方法之后
//调用清空缓存的方法
sqlSession.clearCache();
2、在关闭了sqlSession对象之后
xxxxxxxxxx9 1@Test2public void test(){3 User user = new User();4 user.setId(1L);5 user.setSex(SexEnum.FEMALE);6 int result = userMapper.updateById(user);78 System.out.println(“result:”+result);9}java
开启二级缓存
使用二级缓存需要让被查询的实体实现序列化接口
public class User implements Serializable
设置(settings)
cacheEnabled
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认值是true
<!--开启缓存-->
<setting name="cacheEnabled" value="true"/
在UserMapper.xml中配置
开启对缓存的支持
<!--支持User开启二级缓存-->
<cache></cache>
在需要使用到二级缓存的查询语句中添加useCache
属性,true:使用二级缓存,false:不使用,默认情况下是false
<!--查询所有的用户数据-->
<select id="getUserList" resultMap="userVo" useCache="true">
select * from tb_user
</select>
如何让二级缓存失效
1、在调用了清空缓存的方法之后
2、关闭sqlSessionFactory
Mybatis缓存原理剖析
当开启二级缓存后,Mybatis会为SqlSession对象生成Executor对象时,还会生成一个对象:CachingExecutor,我们称之为装饰者,这里用到了装饰器模式
CachingExecutor的作用是什么呢?
就是当一个查询请求过来时,CachingExecutor会接到请求,先进行二级缓存的查询,如果没命中,就交给真正的Executor
(默认是SimpleExecutor,但是会调用它的父类BaseExecutor的query方法,因为要进行一级缓存的查询)来查询
再到一级缓存中查询,如果还没命中,再到数据库中查询
然后把查询到的结果再返回CachingExecutor,它进行二级缓存,最后再返回给请求方
它是executor的装饰者,增强executor的功能,具有查询缓存的作用
当配置二级缓存后,请求过来时,BaseExecutor这个抽象类会接到请求,就不进行二级缓存的查询
注解方式写SQL
在以后开发中注解方式用的较少
不用写mapper文件
在配置文件中加上mapper映射路径,使用class属性映射到类
<mapper class="com.os467.mapper.UserMapper2"></mapper>
或者使用package,规定映射包路径
<mappers>
<package name="com.os467.mapper"/>
</mappers>
注: mappers标签中mapper和package不能一起用
注解方式
/**
* 查询所有用户数据
*/
@Select("select * from tb_user")
@Results(value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "password",property = "password")
}
)
List<User> getUserList();
高效开发
快速创建mybatis配置模板
在idea的setting中找到Editor
进入File and Code Templates
项
选择Files,+
添加一个模板
Name:mybatis-cfg.xml
Extension:xml
勾上Enable Live Templates
根据接口方法自动生成xml
安装插件 Free Mybatis Tool
在接口名上使用alt+回车
,选择mybatis的xml快速生成,选择mapper文件位置
安装插件后如果xml文件中没有对应接口中的方法,idea也会提示报错
在接口方法上alt+回车
选择生成xml中的方法
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com