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语句和核心代码模块的分离
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
xxxxxxxxxx281 <!--导入MyBatis坐标-->2 <dependency>3 <groupId>org.mybatis</groupId>4 <artifactId>mybatis</artifactId>5 <version>3.5.0</version>6 </dependency>7
8 <!--导入数据库驱动-->9 <dependency>10 <groupId>mysql</groupId>11 <artifactId>mysql-connector-java</artifactId>12 <version>8.0.22</version>13 </dependency>14
15 <!--导入日志依赖,需要log4j.properties文件支持-->16 <dependency>17 <groupId>log4j</groupId>18 <artifactId>log4j</artifactId>19 <version>1.2.17</version>20 </dependency>21
22 <!--单元测试依赖-->23 <dependency>24 <groupId>junit</groupId>25 <artifactId>junit</artifactId>26 <version>4.13.2</version>27 <scope>test</scope>28 </dependency>
xxxxxxxxxx271 23 4 5<configuration>6 <environments default="development">7 <environment id="development">8 <!--9 开启事务,默认开启事务,type:事务类型10 -->11 <transactionManager type="JDBC"/>12 <!--13 配置数据源信息14 POOLED:采用链接池的形式15 -->16 <dataSource type="POOLED">17 <property name="driver" value="${driver}"/>18 <property name="url" value="${url}"/>19 <property name="username" value="${username}"/>20 <property name="password" value="${password}"/>21 </dataSource>22 </environment>23 </environments>24 <mappers>25 <mapper resource="org/mybatis/example/BlogMapper.xml"/>26 </mappers>27</configuration>与spring中的dataSource数据源不同,驱动的name属性不是driverClassName,而是driver,我们要依照MyBatis官网提供的属性名称来写标签
MyBatis采用了池的思想,将数据库链接对象
environment 元素体中包含了事务管理和连接池的配置,mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息
属性(properties)
${}来获取到值)属性配置文件
xxxxxxxxxx41db.url=jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-82db.driver=com.mysql.cj.jdbc.Driver3db.username=root4db.password=rootresource属性:引用外部配置文件,相对路径、绝对路径
xxxxxxxxxx51<!--用于读取外部配置文件-->2<properties resource="db.properties">3 <property name="username" value="root"/>4 <property name="password" value="root"/>5</properties>
在<configuration>标签中配置
类型别名可给类起别名,它仅用于 XML 配置,意在降低冗余的全限定类名书写
这样我们在mapper映射文件中就能使用别名了
xxxxxxxxxx101<!--给实体类定义别名-->2<typeAliases>3
4 <!--给具体的实体定义别名-->5 <typeAlias type="com.os467.pojo.Emp" alias="emp"></typeAlias>6
7 <!--给指定包下面的实体定义别名,默认的别名就是类名,而且是不区分大小写的-->8 <package name="com.os467"></package>9
10</typeAliases>mapper文件中使用别名:
xxxxxxxxxx11<select id="getEmpList" resultType="emp" resultMap="getEmpListMap">
SqlSessionFactory的实例是MyBatis应用的核心,SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先配置的Configuration实例来构建出SqlSessionFactory实例
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置,但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流,MyBatis 包含一个名叫 Resources 的工具类(使用的是org.apache.ibatis.io包下的类),它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易
xxxxxxxxxx81//资源路径2String resource = "org/mybatis/example/mybatis-config.xml";3
4//读取配置文件5InputStream inputStream = Resources.getResourceAsStream(resource);6
7//创建SqlSessionFactory实例8SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
MyBatis在底层会通过动态代理为持久层接口创建对应的实现类(接口代理)
测试类:
xxxxxxxxxx501InputStream resourceAsStream = null;2SqlSession sqlSession = null;3
4try {5
6 //根据流对象来解析mybatis核心配置文件,读取相关数据7 resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");8
9 //创建sqlSessionFactory工厂对象10 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);11
12 //将sqlSession对象从工厂中取出来13 sqlSession = sqlSessionFactory.openSession();14
15 //mybatis在底层会通过动态代理为持久层接口创建对应的实现类16 UserDao userDao = sqlSession.getMapper(UserDao.class);17
18 //调用查询用户信息的方法19 List<User> userList = userDao.getUserList();20
21 //遍历集合22 for (User user : userList) {23
24 System.out.println(user);25
26 }27
28} catch (IOException e) {29 e.printStackTrace();30}finally {31
32 //关闭资源33 if (resourceAsStream != null){34
35 try {36 resourceAsStream.close();37 } catch (IOException e) {38 e.printStackTrace();39 }40
41 }42
43 if (sqlSession != null){44
45 sqlSession.close();46
47 }48
49
50}
mapper文件存放sql资源,在Resource文件下mapper文件资源路径必须和持久层接口的包结构相同
Resource中的包结构以文件夹的形式创建,不能以"."的方式分隔
可以将映射文件命名为持久层接口名+Mapper的形式
xxxxxxxxxx41<!--映射一个mapper文件-->2<mappers>3 <mapper resource=""></mapper>4</mappers>
引入映射文件约束
xxxxxxxxxx91 23 4 5
6<!--namespace就是持久层接口的全类名-->7<mapper namespace="">8
9</mapper>
详细参考官方文档:https://mybatis.net.cn/sqlmap-xml.html
<select> 标签用于编写查询语句
id:必须是接口中的方法名称
resultType属性:需要将映射实体的全类名传过来
resultMap属性:自定义的字段映射规则,如果没有定义的字段就按照默认的映射规则,需要传入resultMap的id值
parameterType属性:如果有参数需要把参数的全类名传过来
xxxxxxxxxx101<mapper namespace="com.os467.dao.UserDao">2
3 <!--查询所有的用户数据-->4 <select id="getUserList">5 6 select * from tb_user7 8 </select>9
10</mapper>
默认映射规则:
解决方案
1、在sql语句中使用别名
这种方式就不能用*,比较麻烦
xxxxxxxxxx21<select id="getEmpList" resultType="com.os467.pojo.Emp"> select emp_name as empName from emp 2</select>2、设置映射结果集,自定义映射关
在select标签中添加resultMap属性,引入自定义的映射结果集
以下将字段中的emp_name映射到实例中的empName,没有设置映射关系的就按照默认的映射规则
xxxxxxxxxx161<select id="getEmpList" resultType="com.os467.pojo.Emp" resultMap="empMap">2
3 select * from emp4
5</select>6
7<!--映射结果集-->8<resultMap id="empMap" type="com.os467.pojo.Emp">9
10 <!--匹配主键字段-->11 <id column="eid" property="id"></id>12 13 <!--匹配非主键字段,column是字段名,property对应属性名-->14 <result column="emp_name" property="empName"></result>15
16</resultMap>
模拟SqlSession中getMapper方法底层:
模拟一个mybatis通过反射创建的持久层接口实现类,通过聚合sqlSession读取mapper文件来映射对应的sql并且返回结果
xxxxxxxxxx311package com.os467.mapper;2
3import com.os467.pojo.User;4import org.apache.ibatis.session.SqlSession;5
6import java.util.List;7
8/**9 * 假如这个类就是mybatis底层创建出来的实现类10 */11public class UserMapperImpl implements UserMapper {12
13 private SqlSession sqlSession;14
15 public UserMapperImpl(SqlSession sqlSession) {16 this.sqlSession = sqlSession;17 }18
19 /**20 * 查询所有的用户数据21 * @return22 */23 public List<User> getUserList() {24
25 //读取mapper文件,根据全局标识来映射到对应的sql,底层类似dom4j解析26 List<User> userList = sqlSession.selectList("com.os467.mapper.UserMapper.getUserList");27
28 return userList;29 }30
31}测试:
xxxxxxxxxx101//创建一个持久层的实现类的实例2UserMapper userMapper = new UserMapperImpl(sqlSession);3
4List<User> userList = userMapper.getUserList();5
6for (User user : userList) {7
8 System.out.println(user);9
10}
单元测试中的Before和After标签:
可以减少测试方法中的代码量
xxxxxxxxxx531InputStream resourceAsStream = null;2SqlSession sqlSession = null;3SqlSessionFactory sqlSessionFactory = null;4
5/**6 * @Before 这个注解的作用:会在所有的测试单元执行之前执行7 */89public void mybatisBefore(){10
11 //根据流对象来解析mybatis核心配置文件,读取相关数据12 try {13
14 resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");15
16 } catch (IOException e) {17 e.printStackTrace();18 }19
20 //创建sqlSessionFactory工厂对象21 sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);22
23 //将sqlSession对象从工厂中取出来24 sqlSession = sqlSessionFactory.openSession();25
26}27
28/**29 * @After 这个注解的作用:会在所有的测试单元执行之后执行30 */3132public void mybatisAfter(){33
34 //关闭资源35 if (resourceAsStream != null){36
37 try {38 resourceAsStream.close();39 } catch (IOException e) {40 e.printStackTrace();41 }42 }43
44 if (sqlSession != null){45
46 try {47 sqlSession.close();48 } catch (Exception e) {49 e.printStackTrace();50 }51 }52
53}
在mybatis中可以通过OGNL表达式去取值
OGNL表达式: #{ } 可以取到实体中某一个属性的值,在执行sql的时候可以防止sql注入
xxxxxxxxxx61<!--根据id来删除员工数据-->2<delete id="deleteEmpByEid" parameterType="com.os467.pojo.Emp">3
4 delete from emp where eid = #{eid}5
6</delete>返回值类型为影响记录条数,可以不用写
在开启事务的条件下,增删改语句后还要提交事务
xxxxxxxxxx21//在关闭资源前,需要提交事务2sqlSession.commit();
xxxxxxxxxx181<!--根据id来修改员工数据-->2<update id="updateEmpByEid" parameterType="com.os467.pojo.Emp">3
4 update emp set5
6 emp_name = #{empName},7
8 age = #{age},9
10 sex = #{sex},11
12 salary = #{salary},13
14 birthday = #{birthday}15
16 where eid = #{eid}17
18</update>
一些情况下,新增一条数据信息,但其主键(id)是数据库自动在数据库生成(自增),而有些业务逻辑的处理是需要要到这个生成的主键(id)
selectKey 会将 SELECT LAST_INSERT_ID() 函数返回的结果放入到实体中
keyProperty:对应的实例中的主键的属性名
keyColumn: 数据库的主键字段名
order:
resultType:返回主键的数据类型
xxxxxxxxxx241<!--添加员工-->2<insert id="addEmp" parameterType="com.os467.pojo.Emp">3
4 <selectKey resultType="java.lang.Integer" keyColumn="eid" keyProperty="eid" order="AFTER">5
6 SELECT LAST_INSERT_ID()7
8 </selectKey>9
10 insert into emp (11 emp_name,12 age,13 sex,14 salary,15 birthday16 )values (17 #{empName},18 #{age},19 #{sex},20 #{salary},21 #{birthday}22 )23
24</insert>
OGNL表达式不支持字符串拼接
所以要在传值的时候就把模糊查询的条件给定义好(百分号要提前写好)
OGNL表达式{}中的内容在此处可以是任意的
OGNL表达式使用的是preparedStatement,是有预编译的
xxxxxxxxxx11empMapper.likeEmpName("%小%");xxxxxxxxxx61<!--根据员工姓名来实现模糊查询的功能-->2<select id="likeEmpName" parameterType="java.lang.String" resultMap="getEmpListMap">3
4 select * from emp where emp_name like #{name}5
6</select>或者使用el表达式
el表达式支持字符串的拼接
但是el表达式必须在{}内部写value
el表达式不能防止sql注入
el表达式在mybatis中是直接使用statement的,没有预编译,但是模糊查询并没有sql注入这一方面的隐患,因此可以使用
xxxxxxxxxx11empMapper.likeEmpName("小");xxxxxxxxxx61<!--根据员工姓名来实现模糊查询的功能-->2<select id="likeEmpName" parameterType="String" resultMap="getEmpListMap">3
4 select * from emp where emp_name like '%${value}%'5
6</select>
resultType是需要写的,与增删改语句的影响记录条数不同
xxxxxxxxxx61<!--求员工的总个数-->2<select id="getEmpCount" resultType="java.lang.Integer">3 4 select count(*) from emp5 6</select>parameterType,resultType属性: 如果是java.lang这个包下的可以直接写类名,基本数据类型也可以直接写,不区分大小写
测试单元
xxxxxxxxxx1901package com.os467.test;2
3import com.os467.mapper.EmpMapper;4import com.os467.mapper.UserMapper;5import com.os467.mapper.UserMapperImpl;6import com.os467.pojo.Emp;7import com.os467.pojo.User;8import org.apache.ibatis.io.Resources;9import org.apache.ibatis.session.SqlSession;10import org.apache.ibatis.session.SqlSessionFactory;11import org.apache.ibatis.session.SqlSessionFactoryBuilder;12import org.junit.After;13import org.junit.Before;14import org.junit.Test;15
16import java.io.IOException;17import java.io.InputStream;18import java.lang.reflect.InvocationHandler;19import java.lang.reflect.Method;20import java.lang.reflect.Proxy;21import java.util.List;22
23public class MybatisTest02 {24
25 InputStream resourceAsStream = null;26 SqlSession sqlSession = null;27 SqlSessionFactory sqlSessionFactory = null;28 EmpMapper empMapper = null;29
30 /**31 * @Before 这个注解的作用:会在所有的测试单元执行之前执行32 */33 34 public void mybatisBefore(){35
36 //根据流对象来解析mybatis核心配置文件,读取相关数据37 try {38
39 resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");40
41 } catch (IOException e) {42 e.printStackTrace();43 }44
45 //创建sqlSessionFactory工厂对象46 sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);47
48 //将sqlSession对象从工厂中取出来49 sqlSession = sqlSessionFactory.openSession();50 51 empMapper = sqlSession.getMapper(EmpMapper.class);52 }53
54 /**55 * @After 这个注解的作用:会在所有的测试单元执行之后执行56 */57 58 public void mybatisAfter(){59
60 //提交事务61 sqlSession.commit();62
63 //关闭资源64 if (resourceAsStream != null){65
66 try {67 resourceAsStream.close();68 } catch (IOException e) {69 e.printStackTrace();70 }71 }72
73 if (sqlSession != null){74
75 try {76 sqlSession.close();77 } catch (Exception e) {78 e.printStackTrace();79 }80 }81
82 }83
84 /**85 * 测试查询功能86 */87
88 89 public void test01(){90
91 List<Emp> empList = empMapper.getEmpList();92
93 for (Emp emp : empList) {94
95 System.out.println(emp);96
97 }98
99 }100
101 /**102 * 测试删除功能103 */104 105 public void test02(){106
107 Emp emp = new Emp();108
109 emp.setEid(4);110
111 int num = empMapper.deleteEmpByEid(emp);112
113 System.out.println(num==1?"删除成功":"删除失败");114
115 }116
117 /**118 * 测试修改功能119 */120 121 public void test03(){122
123 Emp emp = new Emp();124
125 emp.setEid(1);126 emp.setEmpName("张三");127 emp.setSalary(6000);128 emp.setAge(20);129 emp.setSex(2);130 emp.setBirthday("2022-07-12");131
132 int num = empMapper.updateEmpByEid(emp);133
134 System.out.println(num==1?"修改成功":"修改失败");135
136 }137
138 /**139 * 测试添加功能140 */141 142 public void test04(){143
144 Emp emp = new Emp();145
146 emp.setEmpName("小刘");147 emp.setSalary(10000);148 emp.setAge(21);149 emp.setSex(2);150 emp.setBirthday("2022-07-12");151
152 System.out.println(" 添加之前 "+emp);153
154 int num = empMapper.addEmp(emp);155
156 System.out.println(num==1?"添加成功":"添加失败");157
158 System.out.println(" 添加之后 "+emp);159
160 }161
162 /**163 * 测试模糊查询功能164 */165 166 public void test05(){167
168 List<Emp> empList = empMapper.likeEmpName("小");169
170 for (Emp emp : empList) {171
172 System.out.println(emp);173
174 }175
176 }177
178 /**179 * 求总记录条数180 */181 182 public void test06(){183
184 Integer empCount = empMapper.getEmpCount();185
186 System.out.println("员工总个数为"+empCount);187
188 }189
190}
动态SQL指的是根据不同的查询条件,生成不同的Sql语句
我们会先给 where 后面加上一个恒成立的条件 1=1
再在后面使用<if></if>标签设置查询的条件
或者使用<where></where>标签,将<if></if>标签放在<where></where>标签中
xxxxxxxxxx31<where>2 <if test="..">and ...</if>3</where>test属性:会通过参数中实例的get方法获取到属性,对属性值进行测试,如果属性不为空则生成对应的查询条件
xxxxxxxxxx181<!--根据条件来查询数据-->2 <select id="findUser" parameterType="user" resultType="user">3
4 select * from tb_user where 1 = 15
6 <if test="username != null">7
8 and username = #{username}9
10 </if>11 12 <if test="password != null">13 14 and password = #{password}15 16 </if>17
18 </select>
类似java的 switch 语句
xxxxxxxxxx281<!--根据条件来查询数据-->2<select id="findUser" parameterType="user" resultType="user">3
4 select * from tb_user5
6 <where>7 <choose>8
9 <when test="username != null">10
11 and username = #{username}12
13 </when>14
15 <when test="password != null">16
17 and password = #{password}18
19 </when>20
21 <otherwise>22 and id = 123 </otherwise>24
25 </choose>26 </where>27
28</select>和 where 元素等价的自定义 trim 元素为:
xxxxxxxxxx31<trim prefix="WHERE" prefixOverrides="AND |OR ">2 ...3</trim>
用于动态更新语句的类似解决方案叫做 set,set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
xxxxxxxxxx221<!--根据id来修改员工数据-->2<update id="updateEmpByEid" parameterType="com.os467.pojo.Emp">3
4 update emp5
6 <set>7
8 <if test="empName != null">emp_name = #{empName},</if>9
10 <if test="age != null"> age = #{age},</if>11
12 <if test="sex != null"> sex = #{sex},</if>13
14 <if test="salary != null">salary = #{salary},</if>15
16 <if test="birthday != null">birthday = #{birthday},</if>17
18 </set>19
20 where eid = #{eid}21
22</update>这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
与 set 元素等价的自定义 trim 元素
xxxxxxxxxx31<trim prefix="SET" suffixOverrides=",">2 ...3</trim>
xxxxxxxxxx131<!--根据EidS来删除员工数据-->2 <delete id="deleteEmpByEidS" parameterType="java.util.List">3
4 delete from emp 5 where eid in6 <foreach item="item" index="index" collection="list"7 open="(" separator="," close=")">8
9 #{item}10
11 </foreach>12
13 </delete>你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素
xxxxxxxxxx111({"<script>",2 "update Author",3 " <set>",4 " <if test='username != null'>username=#{username},</if>",5 " <if test='password != null'>password=#{password},</if>",6 " <if test='email != null'>email=#{email},</if>",7 " <if test='bio != null'>bio=#{bio}</if>",8 " </set>",9 "where id=#{id}",10 "</script>"})11 void updateAuthorValues(Author author);
详情查看官方文档:https://mybatis.net.cn/dynamic-sql.html
涉及业务的实体都要对应一个接口
一对一:账单对用户,一个账单只能对应一个用户
将账单表看成主表
我们一般在主表中聚合一个从表的引用
xxxxxxxxxx21//聚合从表的引用2private User user;为聚合的实例赋值
需要在resultMap中配置映射关系
配置查询语句
xxxxxxxxxx81<!--查询所有的账单数据-->2<select id="findAccountList" resultMap="accountVo">3
4 select * from ts_account a5 left join tb_user u6 on u.id = a.uid;7
8</select>配置映射关系
association标签
通过多表联查找到的主键id字段来为聚合的实例赋值
property属性:就是引用的属性名称
column属性:匹配的主键id
xxxxxxxxxx241<!--建立起实体属性和表字段的一个对应关系-->2<resultMap id="accountVo" type="account">3
4 <!--匹配主键字段-->5 <id column="tid" property="tid"></id>6
7 <!--匹配非主键字段-->8 <result column="money" property="money"></result>9 <result column="uid" property="uid"></result>10
11 <!--12 配置一对一的关系13 property 就是引用的属性名称14 column 匹配的主键id15 -->16 <association property="user" column="id">17
18 <id column="id" property="id"></id>19 <result column="username" property="username"></result>20 <result column="password" property="password"></result>21
22 </association>23
24</resultMap>
一对多:用户对账单,一个用户可以对应多个账单
用户表看成主表
xxxxxxxxxx21//聚合从表的集合引用2private List<Account> accounts;配置查询语句
xxxxxxxxxx81<!--查询所有的用户数据-->2<select id="getUserList" resultMap="userVo">3
4 select * from tb_user u5 left join ts_account a6 on u.id = a.uid7
8</select>配置映射关系
collection标签
property属性:集合引用的属性名称
ofType属性:集合的泛型的全类名,有别名可以用别名
xxxxxxxxxx241<!--建立起实体属性和表字段的一个对应关系-->2<resultMap id="userVo" type="user">3
4 <!--匹配主键字段-->5 <id column="id" property="id"></id>6
7 <!--匹配非主键字段-->8 <result column="username" property="username"></result>9 <result column="password" property="password"></result>10
11
12 <!--配置一对多的关系-->13 <collection property="accounts" ofType="account">14
15 <!--匹配主键字段-->16 <id column="tid" property="tid"></id>17
18 <!--匹配非主键字段-->19 <result column="money" property="money"></result>20 <result column="uid" property="uid"></result>21
22 </collection>23
24</resultMap>
多对多:用户和角色,一个用户可以对应多个角色,一个角色也可以对应多个用户
例子:
张三:在家庭中是一个父亲,在工作中,是一个项目经理
项目经理:张三,李四
查询语句
xxxxxxxxxx181 <!--查询所有的用户数据-->2 <select id="getUserList" resultMap="userVo">3
4 select u.*,r.*5 from tb_user u6 /*7 先让用户表连接中间表8 */9 left join user_role ur10 on u.id = ur.uid11 /*12 将用户表作为主表建立起与中间表的对应关系13 再将查询的结果作为新表建立起与角色表的对应关系14 */15 left join tb_role r16 on r.rid = ur.rid17
18 </select>配置映射关系
xxxxxxxxxx221<!--建立起实体属性和表字段的一个对应关系-->2<resultMap id="userVo" type="user">3
4 <!--匹配主键字段-->5 <id column="id" property="id"></id>6
7 <!--匹配非主键字段-->8 <result column="username" property="username"></result>9 <result column="password" property="password"></result>10
11 <!--配置多对多的关系-->12 <collection property="roles" ofType="role">13
14 <!--匹配主键字段-->15 <id column="rid" property="rid"></id>16
17 <!--匹配非主键字段-->18 <result column="role_name" property="roleName"></result>19
20 </collection>21
22</resultMap>
什么是延迟加载?
真正在使用数据的时候才发起查询,不用的时候不查询,按需加载(懒加载)
什么是立即加载?
不管用不用,一调用方法,马上发起查询,效率会低于延迟加载
在查询用户数据的时候,有没有必要将账单数据带出来?
没有必要,我们会采用延迟加载的策略
在查询账单数据的时候,有没有必要将用户数据带出来?
有必要,因为在查询账单的时候,需要知道该账单对应的用户是谁,要采用立即加载的策略
配置延迟加载(懒加载)
工程默认情况下采用的是立即加载的策略
设置名:
lazyLoadingEnabled
延迟加载的全局开关,当开启时,所有关联对象都会延迟加载, 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态,默认值就是false
aggressiveLazyLoading
开启时,任一方法的调用都会加载该对象的所有延迟加载属性,否则,每个延迟加载属性会按需加载,默认值false
需要在configuration标签下创建settings标签
xxxxxxxxxx51<settings>2 <!--开启当前工程的延迟加载-->3 <setting name="lazyLoadingEnabled" value="true"/>4 <setting name="aggressiveLazyLoading" value="false"/>5</settings>
查询的时候只查询账户表数据,因为如果使用多表联查是一定会把数据立即查出来的
xxxxxxxxxx51<select id="findAccountList" resultMap="accountVo">2
3 select * from ts_account4
5</select>在association中加入select属性
select属性:提供UserMapper.xml中查询语句的全局标识
column属性:主sql查出来的某一列字段作为参数传递给子sql,提供一对一查询用户表的查询条件
由于全局标识可能会重复,所以我们把名称空间也带上
xxxxxxxxxx161<!--建立起实体属性和表字段的一个对应关系-->2<resultMap id="accountVo" type="account">3
4 <id column="tid" property="tid"></id>5 <result column="money" property="money"></result>6 <result column="uid" property="uid"></result>7
8 <association property="user" column="uid" select="com.os467.mapper.UserMapper.findUserById">9
10 <id column="id" property="id"></id>11 <result column="username" property="username"></result>12 <result column="password" property="password"></result>13
14 </association>15
16</resultMap>UserMapper中新建条件查询sql
xxxxxxxxxx61<!--根据id来查询对应的用户数据-->2<select id="findUserById" parameterType="java.lang.Integer" resultMap="userVo">3
4 select * from tb_user where id = #{id}5
6</select>
只有在存放多表数据的属性需要被用到的时候才会去查询并返回结果(延迟加载)
select属性:提供AccountMapper.xml中查询语句的全局标识
column属性:主sql查出来的某一列字段作为参数传递给子sql,提供一对多查询账户表的查询条件
xxxxxxxxxx201<!--建立起实体属性和表字段的一个对应关系-->2<resultMap id="userVo" type="user">3
4 <id column="id" property="id"></id>5 <result column="username" property="username"></result>6 <result column="password" property="password"></result>7
8 <!--配置一对多的关系-->9 <collection property="accounts" ofType="account" column="id" select="com.os467.mapper.AccountMapper.findAccountByUid">10
11 <!--匹配主键字段-->12 <id column="tid" property="tid"></id>13
14 <!--匹配非主键字段-->15 <result column="money" property="money"></result>16 <result column="uid" property="uid"></result>17
18 </collection>19
20</resultMap>AccountMapper中新建条件查询sql
xxxxxxxxxx51<select id="findAccountByUid" parameterType="java.lang.Integer" resultType="account">2
3 select * from ts_account where uid = #{id}4
5</select>注意在使用resultMap的时候不要在两个表间同时配置对方表的select引用,否则会出现栈溢出死循环
缓存
1、什么是缓存
存在内存中的临时数据
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2、为什么使用缓存
减少和数据库的交互次数,减少系统开销,提高系统效率
3、什么样的数据能使用缓存
经常查询并且不经常改变的数据
关于mybatis中的缓存:一级缓存,二级缓存
一级缓存:sqlSession级别的缓存,这个缓存默认是存在的,在我们进行数据查询的时候,mybatis会先去数据库中将对应的数据查出来,查出来之后,会将数据封装成对象,存到缓存域中,下次再发起查询的时候,mybatis会从缓存域中去读取数据,避免了与数据库的多次交互
二级缓存:sqlSessionFactory级别的缓存,这个缓存可以共享所有的sqlSession,将数据通过序列化的方式存到缓存域中,存放的是数据,不是对象
测试一级缓存
xxxxxxxxxx81//访问数据库2List<User> userList = mapper.getUserList2();3
4//从缓存中查5List<User> userList2 = mapper.getUserList2();6
7//true,内存地址相同,第二次查询是从缓存中取的数据8System.out.println(userList == userList2);
1、在调用了清空缓存的方法之后
xxxxxxxxxx21//调用清空缓存的方法2sqlSession.clearCache();2、在关闭了sqlSession对象之后
3、第二次查询的数据跟第一次查询的数据有偏差(在第一次查询了之后,你去执行了添加、删除、修改等操作)
使用二级缓存需要让被查询的实体实现序列化接口
xxxxxxxxxx11public class User implements Serializable
设置(settings)
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认值是true
xxxxxxxxxx21<!--开启缓存-->2<setting name="cacheEnabled" value="true"/
在UserMapper.xml中配置
开启对缓存的支持
xxxxxxxxxx21<!--支持User开启二级缓存-->2<cache></cache>
在需要使用到二级缓存的查询语句中添加useCache属性,true:使用二级缓存,false:不使用,默认情况下是false
xxxxxxxxxx61<!--查询所有的用户数据-->2<select id="getUserList" resultMap="userVo" useCache="true">3
4 select * from tb_user5
6</select>
1、在调用了清空缓存的方法之后
2、关闭sqlSessionFactory
在以后开发中注解方式用的较少
不用写mapper文件
在配置文件中加上mapper映射路径,使用class属性映射到类
xxxxxxxxxx11<mapper class="com.os467.mapper.UserMapper2"></mapper>
注解方式
xxxxxxxxxx131/**2 * 查询所有用户数据3 */4("select * from tb_user")5(value = {6 7 (id = true, column = "id", property = "id"),8 (column = "username", property = "username"),9 (column = "password",property = "password")10 11 }12)13List<User> getUserList();