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>
- 第三步:配置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>
这里要注意,添加和删除操作针对的是一整个被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的二级缓存容易出现一些脏数据问题,所以我们在工作中一般不使用二级缓存。
-
二级缓存不可靠(理论上可以出现脏数据的问题)
-
二级缓存不可控
二级缓存区的内容,我们用户不能够自己去手动的修改与维护,所以其实对于使用者来说,二级缓存区的内容是透明的,也是不可控的。
-
二级缓存功能不够强大
我们不能对二级缓存里面的内容去做一些特定的功能,例如使用者想让对应的缓存定时过期,定时刷新等等