Mybatis
介绍
基本介绍
Mybatis是一个ORM框架。
ORM:Object relationship mapping,对象关系映射。什么叫对象关系映射呢?其实就是说Mybatis这个框架可以把关系型数据库表中的记录映射对象,可以把对象映射为关系型数据库表中记录。
基础入门
- 第一步:导包:写入maven中的pom.xml
<!-- Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- 数据库驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </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"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 数据库连接的配置--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/practice?useSSL=false&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- 需要把放置SQL语句的的配置文件配置到这里--> <mappers> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <mapper resource="AccountMapper.xml"/> </mappers> </configuration> • 28 • 29
- 第三步:配置Mapper配置文件,这个配置文件里面放置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"> <!-- Namespace:命名空间,在Mybatis指代这个文件,每一个放SQL语句的配置文件都有一个自己的命名空间 --> <mapper namespace="cskaoyan"> <!-- 每一个标签有自己的id值,id值不能重复 resultType是返回类型,配置对应类型的全限定名称 --> <select id="selectAccountById" resultType="com.cskaoyan.vo.Account"> select * from account where id = #{id} </select> <!--<insert id=""--> <!--<delete id=""--> <!--<update id=""--> </mapper>
- 第四步:编写代码
// 获取SqlSessionFactory,需要先获取SqlSessionFactoryBuilder SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 获取主配置文件输入流 // ClassLoader classLoader = MybatisMain.class.getClassLoader(); // InputStream stream = classLoader.getResourceAsStream("mybatis-config.xml"); InputStream stream = Resources.getResourceAsStream("mybatis-config.xml"); // 获取SqlSessionFactory SqlSessionFactory sqlSessionFactory = builder.build(stream); // 获取SqlSession SqlSession可以帮助我们去执行SQL语句 SqlSession sqlSession = sqlSessionFactory.openSession(); // 可以根据SQLsession去执行对应的SQL语句 Account account = sqlSession.selectOne("cskaoyan.selectAccountById", 1); // 打印Account System.out.println(account); // 关闭SQLsession sqlSession.close();
- 注意:Mybatis的SqlSession默认是不自动提交的,所以在执行完之后需要手动提交:sqlSession.commit()
- 设置自动提交的方法
// 获取SqlSession SqlSession可以帮助我们去执行SQL语句 sqlSession = sqlSessionFactory.openSession(); // 表示获取到的SqlSession 是自动提交的 sqlSessionFactory.openSession(true);
动态代理
通过动态代理,可以不指定SQL语句的坐标就能执行对应的SQL语句
使用方式
- 提供一个接口,全限定类名与SQL语句配置文件中的Namespace值一致
- 接口中的方法名与配置文件中的id对应
- 以上两点就完成了SQL语句的坐标的定位
- 方法的返回值要和配置文件中的resultType对应
- 如果方法的返回值是list或者数组,那么resultType中只需要填该数组中对应的对象的全限定类名
- 参数的类型和名字必须要对应
- 接口和其对应的Mapper配置文件建议同名,并且编译后在同一级目录下
- 实现的方法:在resources包下建一个和源代码包中一样的包名
- 在idea中建包的时候要注意不能直接使用com.fh.xxx的形式,应该使用com/fh/xxx
public interface AccountMapper { //自定义的接口 Account selectAccountById(Integer id); }
<!-- Namespace:命名空间,在Mybatis指代这个文件,每一个放SQL语句的配置文件都有一个自己的命名空间 --> <mapper namespace="com.cskaoyan.mapper.AccountMapper"> <select id="selectAccountById" resultType="com.cskaoyan.vo.Account"> select * from account where id = #{id} </select> <insert id="insertAccount"> insert into account values (#{id},#{name},#{money},#{role}) </insert> <!--<insert id=""--> <!--<delete id=""--> <!--<update id=""--> </mapper>
public static void main(String[] args) throws IOException { // AccountMapper accountMapper = new AccountMapperImpl(); SqlSession sqlSession = MybatisUtils.getSqlSession(); // 获取到的是Mybatis帮助我们生成的代理对象,即AccountMapper的实现类 AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);//传入接口的字节码文件 Account account = accountMapper.selectAccountById(2); // sqlSession.selectOne("com.cskaoyan.mapper.AccountMapper.selectAccountById",2); System.out.println(account); }
设置
properties
可以读取外部的properties文件,方便对Mybatis的动态修改
<properties resource="jdbc.properties"/> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/>
settings
设置日志、缓存、懒加载等配置
<settings> <!-- 配置日志--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
类型别名
通过类型别名对我们自定义的类来起别名,使代码在书写的时候更方便,但同时也会降低可读性,所以建议合理的起别名
<typeAliases> <typeAlias type = "com.cskaoyan.vo.Account" alias="a"/> </typeAliases>
一些常见的基本和包装类型还有集合类型有内置的别名,不区分大小写
别名 | 映射的类型 |
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
环境配置
可以针对不同的环境来配置,基本没用
<environments default="development"> <!-- id: 环境的名字--> <environment id="development"> <!-- 事务管理器 JDBC: 使用Connection来管理事务 MANAGED:使用容器(Spring)来管理事务 --> <transactionManager type="JDBC"/> <!-- 数据库连接池的配置 POOLED:使用Mybatis给我们内置的数据库连接池 UNPOOLED:不使用数据库连接池,每次都会新建连接 JNDI:可以支持我们使用外部的数据源(DBCP、C3p0) --> <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> <!--<environment id="test">--> <!--<transactionManager type="JDBC"/>--> <!--<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>--> <!--<environment id="prod">--> <!--<transactionManager type="JDBC"/>--> <!--<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>
映射器
配置各个Mapper.xml映射文件
<mappers> <mapper resource = "com/fh/mapper/UserMapper.xml"/> 直接使用相对路径来读取,只能读取单个文件 </mappers> <mappers> <package name="com.fh.mapper"/> //推荐的用法,这样可以读取mapper包下的所有文件 </mappers>
输入映射
如何将接口中的参数传入配置文件中的预编译代码中
一个简单参数
简单参数 = 基本类型 + 包装类型 + String
mapper
Account selectAccountById(Integer id);
mapper.xml
<select id = "selectAccountById" resultType="xxxx"> select * from account where id = #{id} </select>
传入多个参数
mapper
// 多个参数 // 根据Id修改名字和角色 int updateAccountNameAndRoleById(@Param("id")Integer id, @Param("name") String name, @Param("role") String role);
mapper.xml
<update id="updateAccountNameAndRoleById"> update account set name = #{name},role=#{role} where id = #{id} </update>
需要使用注解值来标明每个参数对应的名字,如果不使用注解就只能使用按顺序读取
传入对象
mapper
// 传入对象 int insertAccount(Account account); // 传入对象 加注解 int insertAccountWithParam(@Param("account") Account account);
mapper.xml
<insert id="insertAccount"> insert into account values (#{id},#{name},#{money},#{role}) </insert> <insert id="insertAccountWithParam"> insert into account values (#{account.id},#{account.name},#{account.money},#{account.role}) </insert>
- 如果没有注解,直接使用#{成员变量名}来取值
- 如果有注解,那么使用#{注解的名字.成员变量名}来取值
传入map
mapper
// 传入Map // 规定:map里面只能传 name 和 role List<Account> selectAccountListByMap(@Param("map") Map<String,Object> map);
mapper.xml
<select id="selectAccountListByMap" resultType="com.cskaoyan.vo.Account"> select * from account where name = #{map.name} or role = #{map.role} </select>
- 当没有注解的时候,直接使用#{key}取值
- 当有注解的时候,使用#{注解的值.key}来取值
按位置来传值
mapper
// 按位置来传值 List<Account> selectAccountListByNameOrRole(String name,String role);
mapper.xml
<select id="selectAccountListByNameOrRole" resultType="com.cskaoyan.vo.Account"> select * from account where name = #{param1} or role = #{param2} <!-- select * from account where name = #{arg0} or role = #{arg1} --> </select>
- #{arg0}、#{arg1}、#{arg2}… 这种方式来取值,arg0代表参数列表第一个参数,arg1代表参数列表第二个参数
- #{param1}、#{param2}、#{param3} … 这种方式来取值,param1代表第一个参数,param2代表第二个参数
${}和#{}的区别
#{} 取值的时候采用的是 PrepareStatement预编译占位的方式去设值,${}是采用 statement 字符串拼接的方式去设值,预编译占位的方式比没有数据库注入的问题,比较安全
${} 来取值这种取值方式有两个使用场景
- 当我们传入表名或者是列名的时候需要使用${} 来取值
- 传入列名
例如当我们使用 group by 或者是order by关键字的时候,需要根据对应的列名来分组或者是排序的时候,这种业务场景下需要传入列名,这个时候就需要使用 ${} 来取值
mapper
// 传入列名 List<Account> selectAccountOrderByColumn(@Param("columnName") String columnName);
- mapper.xml
<select id="selectAccountOrderByColumn" resultType="com.cskaoyan.vo.Account"> select * from account order by ${columnName} desc </select>
- order by、group by、 limit 等等后面跟的值都需要使用 ${} 来取值
- 传入表名因为MySQL的单表是有性能极限的(500w),假如当对应的业务的数据超过500条的时候,假如有1000w条,这个时候就可以把这些数据分到不同的表中来存储。分表之后会带来一些问题,我们对于整体的排序,整体的分组其实是不太方便了业内有两个有名的数据库中间件,帮助我们去解决分库分表之后的一些问题
- MyCat
- Sharing-JDBC
- mapper
// 传入表名 Account selectAccountByIdAndTableName(@Param("tableName") String tableName, @Param("id") Integer id);
- mapper.xml
<select id="selectAccountByIdAndTableName" resultType="com.cskaoyan.vo.Account"> select * from ${tableName} where id = #{id} </select>
总之,如果预编译的语句不能确定整体的查询结果,即有可能会有歧义的时候,就需要使用${},而如果是在过滤的时候就可以使用#{}
即,如果语句中需要 = xxx的时候就写成 = #{xxx}
输出映射
Mybatis如何将值返回
简单类型
- 单个值:直接返回即可,可以合理使用别名
mapper
// 一个简单类型 String selectNameById(@Param("id") Integer id);
- mapper.xml
<select id="selectNameById" resultType="string"> select name from account where id = #{id} </select>
- 多个值:当返回值是一个集合或者数组的时候,resultType里面要写集合或者数组里面单个值的类型
mapper
List<String> selectNameList();
- mapper.xml
<select id="selectNameList" resultType="string"> select name from account </select>
对象
- 单个对象:resultType里面需要填该对象的全限定类名
mapper
Account selectAccountById(@Param("id") Integer id);
- mapper.xml
<select id="selectAccountById" resultType="com.cskaoyan.vo.Account"> select * from account where id = #{id} </select>
- 多个对象:与单个对象一样,resultType里面需要填该对象的全限定类名
mapper
List<Account> selectAccountListByRole(String role);
- mapper.xml
<select id="selectAccountListByRole" resultType="com.cskaoyan.vo.Account"> select * from account where role = #{role} </select>
ResultMap
将表中的列名和对象中的属性映射
- 单个值
mapper
AccountVO selectAccountVOById(@param("id") Integer id)
- mapper.xml
<resultMap id="accountVOMap" type="com.cskaoyan.vo.AccountVO"> <!-- id是指该映射map的id,可以调用,type是该map要映射的对象的全限定类名--> <!-- 主键的映射 --> <!-- id:主键的映射 result:普通列的映射 column:查询结果的表的列名 property:对应的返回结果的成员变量名 --> <id column="id" property="uid"/> <!-- 其他普通列的映射--> <result column="name" property="username"/> <result column="money" property="money"/> <result column="role" property="role"/> </resultMap> <select id="selectAccountVOById" resultMap="accountVOMap"> select id,name,money,role from account where id = #{id} </select>
- 一定要注意列名和对象中属性名的区别,我们在查询的时候一般使用列名,在返回的时候封装进对象才用对象中的属性名
插件
Lombok
可以自动生成类中的常用的方法,后期修改类中属性的时候效果显著
- 导包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
- 在idea中安装插件
- 使用
@Getter @Setter @ToString @Data
Mybatis的插件
MybatisCodeHelperPro 直接在idea中下载
动态SQL
where
- 自动拼接where关键字
mapper.xml
<select id="selectStudentById" resultMap="studentMap"> select * from student <where> id = #{id} </where> </select>
- 可以帮助我们去除和where关键字相邻的and或者是or关键字
- 当where标签中,没有条件符合的时候,不拼接where关键字
mapper
// 根据条件去查询对应的StudentList // 传入一个student对象, // 假如student对象的成员变量有值,那么我们就根据对应的成员变量去匹配 // 假如对应的成员变量没有值,那么我们就不看这个条件 List<Student> selectStudentListBySelective(@Param("student") Student student);
mapper.xml
<select id="selectStudentListBySelective" resultMap="studentMap"> select * from student <where> <if test="student.id!=null"> and id = #{student.id} </if> <if test="student.name!=null"> and name = #{student.name} </if> <if test="student.clazz!=null"> and class = #{student.clazz} </if> <if test="student.score!=null"> and score = #{student.score} </if> </where> </select>
if
根据条件来改变SQL语句
<select id = "selectStudentListByScore" resultMap="studentMap"> select * from student where <if test="score gt 60"> score > #{score} </if> <if test="score lte 60"> score <= #{score} </if> </select>
if标签中使用的是OGNL表达式,这个表达式的类型必须是boolean类型,并且符号也需要使用特定的符号
在xml中写SQL语句时,大于号和小于号也需要使用转义字符
原字符 | 转义字符 | OGNL表达式 |
> | > |
gt |
< | < |
lt |
>= | >= |
gte |
<= | < = |
lte |
!= | != | != |
choose when otherwise
相当于java中的if…else
mapper
// 假如传入的id大于10,那么我们就查询id比10大的学生 // 否则查询id小于等于10 的学生 List<Student> selectStudentListById(@Param("id") Integer id);
mapper.xml
<select id="selectStudentListById" resultMap="studentMap"> select * from student <where> <choose> <when test="id gt 10"> id >10 </when> <otherwise> id <=10 </otherwise> </choose> </where> </select>
trim
修剪,可以增加或者删除指定的字符
mapper
// 修改student对象里面值,根据Id去修改 // 假如student里面成员变量有值,我们就去修改 // 假如没有值,就不修改 int updateStudentByIdSelective(@Param("student") Student student);
mapper.xml
<update id="updateStudentByIdSelective"> update student <!-- suffixOverrides:去除指定的后缀 prefix:增加指定的前缀 suffix:增加指定的后缀 prefixOverrides:去除指定的前缀 --> <trim suffixOverrides="," prefix="set" suffix="" prefixOverrides=""> <if test="student.name!=null"> name = #{student.name}, </if> <if test="student.clazz!=null"> class=#{student.clazz}, </if> <if test="student.score!=null"> score=#{student.score}, </if> </trim> <where> id = #{student.id} </where> </update> • 22 • 23
这里要注意,添加和删除操作针对的是一整个被trim包含的语句,而并非里面的某一句if标签
set
set标签就相当于trim标签的<trim suffixOverrides="," prefix="set">
一般用于update中,这样可以使我们的操作更加方便
mapper
int updateStudentByIdSelectiveUseSet(@Param("student") Student student);
mapper.xml
<update id="updateStudentByIdSelectiveUseSet"> update student <set> <if test="student.name!=null"> name=#{student.name} </if> <if test="student.clazz!=null"> class=#{student.clazz} </if> <if test="student.score!=null"> score=#{student.score} </if> <where> id = #{student.id} </where> </set> </update>
sql-include
可以将sql片段提取出来重复利用
<sql id="base_column"> id,class,name,score </sql>
在调用的时候只需要写入标签名即可,可以代替*
<include refid="base_column"/>
foreach
可以帮助我们在SQL里面循环,一般用于批量的插入
插入一个集合或者数组
mapper
// 批量插入 有注解 int insertStduentList(@Param("studentList") List<Student> studentList); // 批量插入 没有注解 int insertStduentListWithoutParam(List<Student> studentList); // 批量插入 传入数组 有注解 int insertStudentArray(@Param("studentArray") Student[] students); // 批量插入 传入数组 没有注解 int insertStudentArrayWithParam(Student[] students);
mapper.xml
<!-- collection : 传入的集合的名字,假如集合参数没有注解,那么使用 collection | list | array(数组)|arg0(按位置传值) 如果没有注解且使用集合的话,建议使用collection,包容性比较强 item:指集合里面的元素 separator: 分隔,指每一个元素之间用什么分隔 open: 指定循环开头的字符 close: 指定循环结束的字符 index: 指下标 --> <insert id="insertStduentList"> insert into student values <foreach collection="studentList" item = "stu" separator=","> (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score}) </foreach> </insert> <insert id="insertStduentListWithoutParam"> insert into student values <foreach collection="collection" item="stu" separator=","> (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score}) </foreach> </insert> <insert id="insertStudentArray"> insert into student values <foreach collection="studentArray" item="stud" separator=","> (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score}) </foreach> </insert> <insert id="insertStudentArrayWithParam"> insert into student values <foreach collection="array" item = "stu" separator=","> (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score}) </foreach> </insert>
in查询
mapper
// 根据Id的List去查询 StudentList List<Student> selectStudentListByIds(@Param("ids") List<Integer> ids);
mapper.xml
<select id="selectStudentListByIds" resultMap="studentMap"> select <include refid="base_column"/> from student where id in <foreach collection="ids" item="id" separator="," open="(",close=")"> #{id} </foreach> </select>
in传入数组和加不加注解和上面都是一致的
selectKey
可以帮助我们在执行对应的SQL语句之前或者之后执行一条额外的SQL语句,一般是用来获取自增的key值
mapper
// 插入一个Account,并且获取自增的id值 int insertAccount(@Param("account") Account account);
mapper.xml
<insert id="insertAccount"> insert into account values(#{account.id},#{account.name},#{account.money},#{account.role}) <!-- resultType: SQL语句的返回值类型 keyProperty:表示需要把结果封装到哪个参数里面去 order: AFTER:表示在插入的主SQL之前执行 BEFORE:表示在插入的主SQL之后执行 --> <selectKey resultType="int" keyProperty="account.id" order="AFTER"> select LAST_INSERT_ID() </selectKey> </insert>
这里要注意,获取的自增的id是当前插入的这个对象对应的id,并非是下一个自增的id是多少
方法的名字也表示是LAST
获取
方法的返回值依然是和接口中写入的一致,这里返回的自增的id被封装到对象中了,需要通过对象调用来获取
useGeneratedKeys
用来获取自增的主键值,直接写入insert标签中即可
mapper
// 插入一个Account,并且获取自增的id值 int insertAccountUseGeneratedKeys(@Param("account") Account account);
mapper.xml
<insert id = "insertAccountUseGeneratedKeys" useGeneratedKeys="true" keyProperty="account.id"> insert into account values{#{student.id},#{student.name},#{student.money},#{student.role}} </insert>
相当于是使用一个附加属性KeyProperty表示将获取到的key封装到指定的位置
多表查询
一对一
- 建立模型
用户表—>用户详情表
User—>UserDetail - 创建javaBean
User中额外维护一个UserDetail对象作为成员属性 - 分次查询mapper
// 通过用户id查询用于信息以及用户详细信息 // 分次查询 User selectUserByIdWithUserDetail(@Param("id") Integer id);
- mapper.xml
<resultMap id="userMap" type="com.cskaoyan.vo.User"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="nickname" property="nickname"/> <result column="gender" property="gender"/> <result column="age" property="age"/> <!-- 假如是单个对象 使用标签 association property: 指成员变量的名字 JavaType:指成员变量的类型 select:指第二次查询SQL语句的坐标,假如和当前ResultMap在同一个文件内,可以只写Id column:指我们要把哪一列的值传递给第二个SQL语句 --> <association property="userDetail" javaType="com.cskaoyan.vo.UserDetail" select="selectUerDetailByUserId" column="id"/> </resultMap> <select id="selectUerDetailByUserId" resultType="com.cskaoyan.vo.UserDetail"> select id,user_id as userId,height,weight,pic from user_detail where user_id = #{id} </select> <!-- 分次查询 --> <!-- select * from user where id = ? --> <!-- select * from user_detail where user_id = ? --> <select id="selectUserByIdWithUserDetail" resultMap="userMap"> select <include refid="base_column"/> from user where id = #{id} </select>
- 总结分次查询的步骤:
- 首先写查询入口,明确终极目标是返回一个什么值,并且要根据传入的参数过滤,创建一个resultMap用来映射返回结果
- 编写resultMap,前面的成员属性正常映射即可,对象属性使用association标签来维护,如果是分次查询的话则该标签直接以
/
作为结束,property属性表示我们需要映射的对象,javaType表示该对象对应的全限定类名,select表示查询该对象的语句,column表示我们要从当前的列表中传入一列的值给第二个SQL语句作为关联,类似连表查询,一般是传id - 最后编写select第二个SQL语句,此时要注意列名是否与对象中的属性对应,如果不一致可以通过起别名或者resultMap映射来解决
- 连接查询mapper
// 连接查询 User selectUserByIdWithUserDetailUseCross(@Param("id") Integer id);
- mapper.xml
<!-- 连接查询 --> <resultMap id="userCrossMap" type="com.cskaoyan.vo.User"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="nickname" property="nickname"/> <result column="gender" property="gender"/> <result column="age" property="age"/> <association property="userDetail" javaType="com.cskaoyan.vo.UserDetail"> <id column="did" property="id"/> <result column="userId" property="userId"/> <result column="height" property="height"/> <result column="weight" property="weight"/> <result column="pic" property="pic"/> </association> </resultMap> <select id="selectUserByIdWithUserDetailUseCross" resultMap="userCrossMap"> SELECT user.id AS id, user.username AS username, user.nickname AS nickname, user.gender AS gender, user.age AS age, user_detail.id AS did, user_detail.user_id AS userId, user_detail.height AS height, user_detail.weight AS weight, user_detail.pic AS pic FROM user LEFT OUTER JOIN user_detail ON user.id = user_detail.user_id WHERE user.id = #{id} </select>
- 连接查询的步骤:
- 写出连接查询的sql语句,并在navicat中检测是否有误
- 根据列名来进行映射即可,要注意此时的association标签是成对出现的property表示关联的对象的成员名称,javaType表示该对象的全限定类名
- 在association标签中直接根据查询的列名的结果对该对象进行映射,方式与普通对象一致
一对多
- 建立模型
班级表---->学生表
- 创建对象
在班级的对象中维护一个studentList,表示一个班有多个学生,即维护在一的一方;在学生的属性中维护一个班级id,依次达到连接的效果
- 分次查询
mapper
// 一对多的分次查询 // 通过班级Id查询出班级的信息以及班级的学生信息 Clazz selectClazzById(@Param("id") Integer id);
- mapper.xml
<!-- 映射关系 --> <resultMap id="clazzMap" type="com.cskaoyan.vo.Clazz"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="studentList" ofType="com.cskaoyan.vo.Student" select="selectStduentListByClazzId" column="id"/> </resultMap> <!-- 第二次查询 --> <select id="selectStduentListByClazzId" resultType="com.cskaoyan.vo.Student"> select id,name,gender,age,clazz_id as clazzId from student where clazz_id = #{id} </select> <!-- 一对多的分次查询入口 --> <select id="selectClazzById" resultMap="clazzMap"> select id,name from clazz where id = #{id} </select>
分次查询的步骤:
- 先写出入口,SQL语句过滤出要查询的班级的信息,使用resultMap映射
- 在resultMap中将班级的信息映射完毕后,studentList需要使用collection标签来进行关联,这里采用分次查询,所以直接用
/
结尾,property还是填要关联的对象属性名,注意集合类中要使用ofType来表示该对象的全限定类名,select语句表示查询该对象的语句,column表示传给第二个语句的值,这里是根据班级号来查询,所以传id - 写第二个查询语句,我们在第一个查询语句中传出的值是班级id,所以第二个语句要根据学生表中保存的班级id来过滤,得到的最终结果直接封装进对象即可,如果使用resultType要写全限定类名,列表不同需要起别名,否则就要使用resultMap进行映射
连接查询
mapper
// 一对多的连接查询 Clazz selectClazzByIdUseCrossQuery(@Param("id") Integer id);
mapper.xml
<resultMap id="clazzCrossMap" type="com.cskaoyan.vo.Clazz"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="studentList" ofType="com.cskaoyan.vo.Student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="age" property="age"/> <result column="gender" property="gender"/> <result column="clazzId" property="clazzId"/> </collection> </resultMap> <!-- 连接查询 --> <select id="selectClazzByIdUseCrossQuery" resultMap="clazzCrossMap"> SELECT clazz.id AS id, clazz.name AS name, student.id AS sid, student.NAME AS sname, student.age AS age, student.gender AS gender, student.clazz_id AS clazzId FROM clazz LEFT OUTER JOIN student ON clazz.id = student.clazz_id WHERE clazz.id = #{id} </select>
连接查询的步骤:基本与一对一的情况一致,唯一要注意的就是collection标签中要用ofType表示集合中单个对象的全限定类名
多对多
- 建立模型
学生表—>选课表---->课程表 - 创建javaBean
在学生的对象中维护一个课程的列表表示选课信息,注意不能相互维护,否则会无限循环 - 分次查询
// 分次查询 根据学生id查询出学生信息以及其课程信息 Student selectStudentWithCourseListById(@Param("id") Integer id);
mapper.xml
<!-- 映射关系 --> <resultMap id="studentMap" type="com.cskaoyan.vo.Student"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="gender" property="gender"/> <result column="age" property="age"/> <result column="clazz_id" property="clazzId"/> <collection property="courseList" ofType="com.cskaoyan.vo.Course" select="selectCourseListByClazzId" column="id"/> </resultMap> <!-- 第二次查询 --> <select id="selectCourseListByClazzId" resultType="com.cskaoyan.vo.Course"> SELECT c.id as id, c.name as name, c.teacher_name as teacherName, c.score as score FROM s_c AS sc LEFT JOIN course AS c ON sc.cid = c.id WHERE sc.sid = #{id} </select> <!-- 分次查询的入口--> <select id="selectStudentWithCourseListById" resultMap="studentMap"> select id,name,gender,age,clazz_id from student where id = #{id} </select>
分次查询的步骤:与一对多基本一致,在第二个语句中要注意明确需要返回什么,然后进行连表查询
- 连接查询
mapper
// 连接查询 根据学生id查询出学生信息以及其课程信息 Student selectStudentWithCourseListByIdUseCrossQuery(@Param("studentId") Integer studentId);
mapper.xml
<resultMap id="studentCrossMap" type="com.cskaoyan.vo.Student"> <id column="sid" property="id"/> <result column="sname" property="name"/> <result column="age" property="age"/> <result column="gender" property="gender"/> <result column="clazzId" property="clazzId"/> <collection property="courseList" ofType="com.cskaoyan.vo.Course"> <id column="cid" property="id"/> <result column="cname" property="name"/> <result column="teacherName" property="teacherName"/> <result column="score" property="score"/> </collection> </resultMap> <!-- 连接查询 --> <select id="selectStudentWithCourseListByIdUseCrossQuery" resultMap="studentCrossMap"> SELECT student.id AS sid, student.name AS sname, student.age AS age, student.gender AS gender, student.clazz_id AS clazzId, course.id AS cid, course.name AS cname, course.teacher_name AS teacherName, course.score AS score FROM student LEFT JOIN s_c ON student.id = s_c.sid LEFT JOIN course ON s_c.cid = course.id WHERE student.id = #{studentId} </select>
懒加载
在进行多表的分次查询的时候,假如第二次查找的信息我们暂时不需要的话,就可以暂时不执行,等到需要的时候再执行查询,可以提高性能
懒加载有总开关和局部开关,当冲突的的时候以局部开关为准
- 总开关
表示开启全局的懒加载
<!-- 懒加载 总开关--> <setting name="lazyLoadingEnabled" value="true"/>
- 局部开关
在association标签中增加一个属性,fetchType=‘’
eager:关闭懒加载
lazy:开启懒加载
缓存
一级缓存
Mybatis的一级缓存是一个SQLSession级别的缓存,默认是开启的,并且Mybatis没有给我们提供关闭一级缓存的方式。
在使用同一个SQLSession去查询同样的数据的时候,第一次查询会查询数据库,然后将这个查询的结果保存到属于自己的内存空间里面,当再次查询的时候如果参数一样,会直接返回内存里的内容,不必再次查询数据库。当执行SQLSession.commit()的时候一级缓存会被清空
一级缓存失效的场景
- 使用不同的SQLSession
- SQLSession提交或者关闭
二级缓存
Mybatis的二级缓存是一个Namespace级别的缓存,默认是关闭的。
总开关:
<!-- 二级缓存总开关 默认值是true--> <setting name="cacheEnabled" value="true"/>
局部缓存:
开启对应的mapper.xml中局部缓存
仅仅只需要在mapper.xml中加上一个标签即可
<cache/>
- 我们需要对对应的对象实现序列化接口
Mybatis的一级缓存整体上来说是稍微有一点用的,可以帮助我们减轻一部分数据库的压力。但是Mybatis的二级缓存容易出现一些脏数据问题,所以我们在工作中一般不使用二级缓存。
- 二级缓存不可靠(理论上可以出现脏数据的问题)
- 二级缓存不可控
二级缓存区的内容,我们用户不能够自己去手动的修改与维护,所以其实对于使用者来说,二级缓存区的内容是透明的,也是不可控的。 - 二级缓存功能不够强大
我们不能对二级缓存里面的内容去做一些特定的功能,例如使用者想让对应的缓存定时过期,定时刷新等等