一、Mybatis入门
1. jdbc编程问题总结
1.1 jdbc程序
@Override
public void insertUser(User user){
Connection conn = DBUtil.getConnection();
PreparedStatement stmt = null;
String sql = "insert into sys_user(NAME,ACCT,PWD,CRTIME,UPTIME) values(?a,?c,?d,now(),now())";
try {
stmt = conn.prepareStatement(sql);
stmt.setString(1, user.getName());
stmt.setString(2, user.getAcct());
stmt.setString(3, user.getPwd());
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.closeAll(conn, stmt, null);
}
}
1.2 jdbc编程步骤
- 加载数据库驱动
- 创建并获取数据库链接
- 创建PreparedStatement对象
- 设置Sql语句中的占位符参数
- 通过PreparedStatement执行Sql并获取结果
- 对Sql执行结果进行解析处理
- 释放资源(Connection、Preparedstatement、ResultSet)
1.3 jdbc问题总结如下
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题
- Sql语句在代码中硬编码,造成代码不易维护,实际应用Sql变化的可能较大,Sql变动需要改变java代码
- 使用PreparedStatement向占位符号传参数存在硬编码,因为Sql语句的where条件不一定,可能多也可能少,修改Sql还要修改代码,系统不易维护
- 对结果集解析存在硬编码(查询列名),Sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便
2. MyBatis介绍
MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis,实质上MyBatis对iBatis进行一些改进
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建Connection、创建Statement、手动设置参数、结果集检索等jdbc繁杂的过程代码
Mybatis通过xml或注解的方式将要执行的各种Statement(Statement、PreparedStatemnt、CallableStatement)配置起来,并通过java对象和Statement中的Sql进行映射生成最终执行的Sql语句,最后由MyBatis框架执行Sql并将结果映射成java对象并返回
3. MyBatis架构
![images](img/mybatis_structure.png)
- MyBatis配置文件mybatis-config.xml(名称不固定),此文件作为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息。mapper.xml文件即Sql映射文件,文件中配置了操作数据库的Sql语句。此文件需要在mybatis-config.xml中加载
- 通过MyBatis环境等配置信息构造SqlSessionFactory即会话工厂
- 由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行
- MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器
- Mapped Statement也是MyBatis一个底层封装对象,它包装了MyBatis配置信息及Sql映射信息等。mapper.xml文件中一个Sql对应一个Mapped Statement对象,Sql的id即是Mapped statement的id
- Mapped Statement对Sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行Sql前将输入的java对象映射至Sql中,输入参数映射就是jdbc编程中对PreparedStatement设置参数
- Mapped Statement对Sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程
4. 搭建MyBatis项目
4.1 建立Java项目
MyBatis是一个持久层框架,操作数据库时使用的。无需创建JavaWEB项目,建立Java项目即可
4.2 导入MyBatis框架jar包
- mybatis-3.4.6.jar为核心jar包,必须引入
- lib目录下的jar包为工具包,可有可无
- mysql或oracle数据库的驱动包,必须引入
maven依赖配置
<dependencies>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
4.3 编写MyBatis中全局配置文件
作用:配置了数据源、事务等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>
<!-- 配置环境:与数据库的连接信息
此配置了解即可,后期与spring框架整合之后,此配置会消失
-->
<environments default="mysql">
<environment id="oracle">
<!-- 配置事务:使用jdbc的事务管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源:连接数据库的信息
type : 表示连接是否使用连接池,POOLED表示mybatis中自带的连接池
-->
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
<property name="username" value="tom"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
4.4 编写实体类
public class Emp {
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private Double sal;
private Double comm;
//get,set方法省略...
}
4.5 编写映射文件
<?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文件进行分类管理
注意:如果使用mapper代理的方式进行开发,namespace有特殊的作用
-->
<mapper namespace="emp">
<!-- 在映射文件中编写sql语句 -->
<!-- 1.通过主键查询员工信息 -->
<!--
通过<select>标签编写查询语句
id : 映射文件中SQL语句的唯一标识
mybatis会将SQL语句封装到Mapped Statement对象中,所以此处的id也可以标识Mapped Statement对象的id
注意:同一个mapper文件中id不能重复,而且id在mapper代理模式下有着重要作用
parameterType : 输入参数的类型
sql语句的占位符:#{}
#{empno}:其中empno表示接收输入的参数值,参数名称为empno
但是如果参数的类型为简单类型(基本数据类型+包装类+字符串类型),参数名称可以任意指定
resultType : 输出参数的类型
需要指定输出数据为Java中的数据类型(实体类的全限定名)
-->
<select id="findEmpById" parameterType="java.lang.Integer" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp where empno=#{abc}
</select>
</mapper>
4.6 加载映射文件
在MyBatis中全局配置文件中添加映射文件位置
<!-- 加载映射文件的位置 -->
<mappers>
<mapper resource="com/mybatis/entity/Emp.xml"/>
</mappers>
4.7 编写测试程序
//1.创建读取全局配置文件的流
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.通过配置文件流创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.通过会话工厂创建会话对象(SqlSession)
SqlSession session = factory.openSession();
/*
4.通过会话对象操作数据库
selectOne(String statementId, Object param)查询单条数据
参数1:映射文件中的statementId,命名空间名.statementId
参数2:向sql语句中传入的数据,注意:传入的数据类型必须与映射文件中配置的parameterType保持一致
返回值:就是映射文件中配置的resultType的类型
*/
Emp emp = session.selectOne("emp.findEmpById",8001);
System.out.println(emp);
//5.关闭资源
session.close();
5. 增删改查的基本操作
5.1 查询操作
mapper文件:
<!--
查询到数据返回多条记录,每一条封装在一个实体类对象中,所有的实体类对象封装在List集合中
resultType :指定的并不是集合的类型,而是单条数据所对应实体类类型
resultType="java.util.List" 错误的配置方式
-->
<select id="findEmp" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp order by empno desc
</select>
Java代码:
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
List<Emp> list = session.selectList("emp.findEmp");
for (Emp e : list) {
System.out.println(e);
}
session.close();
5.2 新增操作
mapper文件:
<insert id="insertEmp" parameterType="com.mybatis.entity.Emp" >
insert into emp(empno,ename,job,mgr,hiredate,sal,comm)
values(#{empno},#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm})
</insert>
Java代码:
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
Emp emp = new Emp();
emp.setEname("tom");
emp.setJob("clrek");
emp.setMgr(1);
emp.setHiredate(new Date());
emp.setSal(6500.0);
emp.setComm(1200.0);
//在mybaits中事务默认需要手动提交
int result = session.insert("emp.insertEmp",emp);
System.out.println(result);
//执行提交的方法
session.commit();
session.close();
插入数据的主键返回:
- select last_insert_id(),表示得到刚insert进去记录的主键值,适用与自增主键的数据库
- select seq_demo.nextval from dual,表示获取下一个序列生成的值,适用于存在序列的数据库
- keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性
- order:selectKey标签中Sql语句,相对于insert语句来说的执行顺序
- resultType:指定selectKey标签中Sql语句的结果类型
<!--mysql-->
<selectKey resultType="java.lang.Integer" keyProperty="empno" order="AFTER">
select last_insert_id()
</selectKey>
<!--oracle-->
<selectKey resultType="java.lang.Integer" keyProperty="empno" order="BEFORE">
select seq_demo.nextval from dual
</selectKey>
如果是主键自增型数据库还可以使用:
<insert id="insertEmp" parameterType="com.mybatis.entity.Emp" keyProperty="empno" useGeneratedKeys="true">
5.3 修改操作
mapper文件:
<update id="updateEmp" parameterType="com.mybatis.entity.Emp">
update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm}
where empno=#{empno}
</update>
Java代码:
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
Emp emp = new Emp();
emp.setEmpno(52);
emp.setEname("jerry");
emp.setJob("manager");
emp.setMgr(10);
emp.setHiredate(new Date());
emp.setSal(8500.0);
emp.setComm(1200.0);
int result = session.update("emp.updateEmp",emp);
System.out.println(result);
//执行提交的方法
session.commit();
session.close();
5.4 删除操作
mapper文件:
<delete id="deleteEmp" parameterType="java.lang.Integer">
delete from emp where empno=#{empno}
</delete>
Java代码:
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
int result = session.delete("emp.deleteEmp",8000);
System.out.println(result);
//执行提交的方法
session.commit();
session.close();
5.5 条件查询
mapper文件:
<!--
#{}占位符,需要在Java中将传入数据的前后拼接%符号
${}Sql语句拼接,在其中只能写value
-->
<select id="findEmpByEname" parameterType="java.lang.String" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where ename like #{abc}
</select>
<select id="findEmpByEname2" parameterType="java.lang.String" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where ename like '%${value}%'
</select>
Java代码:
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
List<Emp> list = session.selectList("emp.findEmpByEname", "%tom%");
//List<Emp> list = session.selectList("emp.findEmpByEname", "tom");
for (Emp e : list) {
System.out.println(e);
}
session.close();
5.6 总结
5.6.1 parameterType和resultType
- parameterType:指定输入参数类型,MyBatis通过ognl从输入对象中获取参数值设置在Sql中
- resultType:指定输出结果类型,MyBatis将Sql查询结果的一行记录数据映射为resultType指定类型的对象
5.6.2 #{} 和 ${}
-
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型、pojo、HashMap
#{}接收简单类型,#{}中可以写成value或其它名称
#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值 -
${}表示一个拼接符号,会引用Sql注入,所以不建议使用${}
${}接收输入参数,类型可以是简单类型、pojo、HashMap
${}接收简单类型,${}中只能写成value
${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值
5.6.3 selectOne和selectList
- selectOne表示查询出一条记录进行映射
如果使用selectOne可以实现使用selectList也可以实现(list中只有一个对象) - selectList表示查询出一个列表(多条记录)进行映射
如果使用selectList查询多条记录,不能使用selectOne
如果使用selectOne报错:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4
6. MyBatis和Hibernate本质区别和应用场景
- Hibernate:是一个标准ORM框架(对象关系映射)
入门门槛较高的,不需要程序写Sql,Sql语句自动生成,对sql语句进行优化、修改比较困难的
应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,erp、crm、oa… - MyBatis:专注是Sql本身,需要程序员自己编写Sql语句,Sql修改、优化比较方便
MyBatis是一个不完全的ORM框架,虽然程序员自己写Sql,MyBatis也可以实现映射(输入映射、输出映射)
应用场景:适用与需求变化较多的项目,比如:互联网项目 - 企业进行技术选型,以低成本高回报作为技术选型的原则,根据项目组的技术力量进行选择
二、MyBatis开发DAO
1. MyBatis API
1.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量
1.2 SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory
1.3 SqlSession
SqlSession是一个面向用户(程序员)的接口,其中提供了很多操作数据库的方法。如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、insert、update、delete。
SqlSession的实例不能共享使用,它是线程不安全的,每个线程都应该有它自己的SqlSession实例,因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
2. MyBatis工具类
为了简化MyBatis的开发,可将MyBatis进一步封装
public class MybatisUtil {
/**
* 不让用户在外界创建工具类对象
*/
private MybatisUtil(){}
/**
* 初始化工厂
*/
private static SqlSessionFactory factory;
static {
try {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取SqlSession对象的方法
*/
public static SqlSession openSession(){
return factory.openSession();
}
}
3. 原始DAO开发方式
原始Dao开发方法需要程序员编写Dao接口和Dao实现类
接口:
public interface DeptDao {
public List<Dept> findDept();
public Dept findDeptById(Integer deptno);
public void insertDept(Dept dept);
public void updateDept(Dept dept);
public void deleteDept(Integer deptno);
}
实现类:
public class DeptDaoImpl implements DeptDao {
@Override
public List<Dept> findDept() {
SqlSession session = MybatisUtil.openSession();
List<Dept> list = session.selectList("dept.findDept");
session.close();
return list;
}
@Override
public Dept findDeptById(Integer deptno) {
SqlSession session = MybatisUtil.openSession();
Dept dept = session.selectOne("dept.findDeptById",deptno);
session.close();
return dept;
}
@Override
public void insertDept(Dept dept) {
SqlSession session = MybatisUtil.openSession();
session.insert("dept.insertDept",dept);
session.commit();
session.close();
}
@Override
public void updateDept(Dept dept) {
SqlSession session = MybatisUtil.openSession();
session.update("dept.updateDept",dept);
session.commit();
session.close();
}
@Override
public void deleteDept(Integer deptno) {
SqlSession session = MybatisUtil.openSession();
session.delete("dept.deleteDept",deptno);
session.commit();
session.close();
}
}
原始DAO开发问题
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量
- 调用SqlSession方法时将Statement的id硬编码了
- 调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发
4. Mapper代理方式
Mapper代理开发方式只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法
程序员编写Mapper接口需要遵循一些开发规范,MyBatis可以自动生成Mapper接口实现类代理对象
开发规范:
- 在mapper.xml中namespace等于Mapper接口的全限定名
- Mapper接口中的方法名和mapper.xml中statement的id一致
- Mapper接口中的方法输入参数类型和mapper.xml中parameterType指定的类型一致
- Mapper接口中的方法返回值类型和mapper.xml中resultType指定的类型一致
Mapper接口:
public interface EmpMapper {
public List<Emp> findEmp();
public Emp findEmpByEmpno(Integer empno);
/**
* 增删改方法的返回值
* 1.void 无返回值
* 2.int 返回操作数据库表的记录数
* 3.boolean 根据返回数据库表影响的记录数,来判断是否成功
* 注意:影响的记录数大于等于1,返回值为true,否则为false
*/
public void insertEmp(Emp emp);
public void updateEmp(Emp emp);
public void deleteEmp(Integer empno);
}
映射文件:
<!--namespace : 与Mapper接口的全限定名保持一致-->
<mapper namespace="com.mybatis.mapper.EmpMapper">
<!--
statementId与Mapper接口的方法名称保持一致
parameterType的类型必须与方法的参数类型保持一致
resultType的类型必须与方法的返回值类型保持一致
-->
<select id="findEmpByEmpno" parameterType="java.lang.Integer" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp where empno=#{abc}
</select>
<!-- 2.查询所有员工 -->
<select id="findEmp" resultType="com.mybatis.entity.Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp order by empno desc
</select>
<!-- 4.新增员工 -->
<insert id="insertEmp" parameterType="com.mybatis.entity.Emp" >
insert into emp(ename,job,mgr,hiredate,sal,comm)
values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm})
</insert>
<!-- 5. 修改员工-->
<update id="updateEmp" parameterType="com.mybatis.entity.Emp">
update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},
sal=#{sal},comm=#{comm} where empno=#{empno}
</update>
<!-- 6. 删除员工-->
<delete id="deleteEmp" parameterType="java.lang.Integer">
delete from emp where empno=#{empno}
</delete>
</mapper>
测试:
SqlSession session = MybatisUtil.openSession();
//通过SqlSession对象获得到你指定Mapper接口的代理对象(就是接口的实现类对象)
EmpMapper mapper = session.getMapper(EmpMapper.class);
三、mybatis-config.xml
MyBatis的全局配置文件mybatis-config.xml,配置内容如下:
- properties(属性)
- settings(全局配置参数)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理)
- dataSource(数据源)
- environment(环境子属性对象)
- mappers(映射器)
1. properties(属性)
将数据库连接参数单独配置在db.properties中,只需要在mybatis-config.xml中加载db.properties的属性值。在mybatis-config.xml中就不需要对数据库连接参数硬编码
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=123456
在mybatis-config.xml加载属性文件:
<!-- 加载外部资源文件 -->
<properties resource="db.properties">
<!--properties中还可以配置一些属性-->
<!--<property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis"/>-->
</properties>
注意: MyBatis将按照下面的顺序来加载属性:
- 在properties标签体内定义的属性首先被读取
- 然后会读取properties标签中resource或url加载的属性,它会覆盖已读取的同名属性
- 最后读取parameterType传递的属性,它会覆盖已读取的同名属性
建议:
- 不要在properties标签体内添加任何属性值,只将属性值定义在properties文件中
- 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
2. settings(全局配置参数)
MyBatis框架在运行时可以调整一些运行参数
比如:开启二级缓存、开启延迟加载
可配置参数如下:
3. typeAliases(类型别名)
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发
3.1 MyBatis默认支持别名
别名 | 映射的类型 | 别名 | 映射的类型 |
---|---|---|---|
_byte | byte | byte | java.lang.Byte |
_short | short | short | java.lang.Short |
_int | int | int | java.lang.Integer |
_integer | int | integer | java.lang.Integer |
_long | long | long | java.lang.Long |
_float | float | float | java.lang.Float |
_double | double | double | java.lang.Double |
_boolean | boolean | boolean | java.lang.Boolean |
string | java.lang.String | date | java.util.Date |
map | java.util.Map | hashmap | java.util.HashMap |
list | java.util.List | arraylist | java.util.ArrayList |
object | java.lang.Object |
3.2 自定义别名
单个定义别名:
<typeAliases>
<!--
<typeAlias>单个别名配置
type : Java中类型的全限定名
alias : 自定义别名
-->
<typeAlias type="com.mybatis.entity.Dept" alias="abc"/>
</typeAliases>
<!--在resultType使用的abc为自定义类型别名-->
<select id="findDeptById" parameterType="int" resultType="abc">
select deptno,dname,loc from dept where deptno=#{deptno}
</select>
批量定义别名:
<typeAliases>
<!--
<package>批量别名配置
name : 需要配置别名的实体类所在包的包名
默认别名为该类的类名,其首字母大小均可
-->
<package name="com.mybatis.entity"/>
</typeAliases>
4. typeHandlers(类型处理器)
MyBatis中通过typeHandlers完成jdbc类型和Java类型的转换,MyBatis自带的类型处理器基本上满足日常需求,不需要单独定义
MyBatis支持类型处理器:
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | Boolean,boolean | 任何兼容的布尔值 |
ByteTypeHandler | Byte,byte | 任何兼容的数字或字节类型 |
ShortTypeHandler | Short,short | 任何兼容的数字或短整型 |
IntegerTypeHandler | Integer,int | 任何兼容的数字和整型 |
LongTypeHandler | Long,long | 任何兼容的数字或长整型 |
FloatTypeHandler | Float,float | 任何兼容的数字或单精度浮点型 |
DoubleTypeHandler | Double,double | 任何兼容的数字或双精度浮点型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的数字或十进制小数类型 |
StringTypeHandler | String | CHAR和VARCHAR类型 |
ClobTypeHandler | String | CLOB和LONGVARCHAR类型 |
NStringTypeHandler | String | NVARCHAR和NCHAR类型 |
NClobTypeHandler | String | NCLOB类型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB和LONGVARBINARY类型 |
DateTypeHandler | java.util.Date | TIMESTAMP类型 |
DateOnlyTypeHandler | java.util.Date | DATE类型 |
TimeOnlyTypeHandler | java.util.Date | TIME类型 |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP类型 |
SqlDateTypeHandler | java.sql.Date | DATE类型 |
SqlTimeTypeHandler | java.sql.Time | TIME类型 |
ObjectTypeHandler | 任意 | 其他或未指定类型 |
EnumTypeHandler | Enumeration类型 | VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引) |
5. mappers(映射器)
5.1 单个加载映射文件
通过resource加载单个映射文件
<mappers>
<!--
<mapper>单个加载映射文件
resource : 通过相对路径加载文件(项目源目录下的文件)
url : 通过绝对路径加载文件(文件系统中文件)
-->
<mapper resource="com/mybatis/mapper/DeptMapper.xml"/>
<mapper resource="com/mybatis/mapper/EmpMapper.xml"/>
</mappers>
通过mapper接口加载单个映射文件
- 前提:使用的是mapper代理方法
- 遵循规范:需要将Mapper接口类名和mapper.xml映射文件名称保持一致且在一个目录中
<mappers>
<!--
<mapper>单个加载映射文件
class : 配置mapper接口的全限定名,通过Java中的Mapper接口来加载映射文件
-->
<mapper class="com.mybatis.mapper.DeptMapper"/>
<mapper class="com.mybatis.mapper.EmpMapper"/>
</mappers>
5.2 批量加载映射文件
指定Mapper接口的包名,MyBatis自动扫描包下边所有Mapper接口进行加载
遵循规范:与mapper接口加载单个映射文件一致
<mappers>
<!--
<package>批量加载映射文件
name : 存放Mapper接口与mapper.xml文件的包名
-->
<package name="com.mybatis.mapper"/>
</mappers>
四、mapper.xml
mapper.xml映射文件中定义了操作数据库的Sql,每个Sql是一个statement,映射文件是MyBatis的核心
1. parameterType输入映射
1.1 简单类型
<select id="findUsersByRealname" parameterType="string" resultType="Users">
select userid,username,password,realname from users where realname like #{realname}
</select>
1.2 实体类或pojo类型
开发中通过实体类或pojo类型传递查询条件,查询条件是综合的查询条件,不仅包括实体类中查询条件还包括其它的查询条件,这时可以使用包装对象传递输入参数
实体类:Users和Pagebean
//实体类
public class Users{
private Integer userid;
private String username;
private String password;
private String realname;
//get,set方法省略...
}
//页时使用的pojo对象
public class Pagebean {
private int page;
private int pageSize;
private int maxCount;
private int maxPage;
private int offset;
//get,set方法省略...
}
复合实体类:UsersQuery
public class UsersQuery {
private Users users;
private Pagebean pagebean;
//get,set方法省略...
}
映射文件:
<select id="findUsersByRealnameAndPage" parameterType="UsersQuery" resultType="Users">
select
userid,username,password,realname from users
where
realname like #{users.realname}
order by userid
limit #{pagebean.offset},#{pagebean.pageSize}
</select>
1.3 Map类型
<select id="findUsersWithMap" parameterType="map" resultType="Users">
select
userid,username,password,realname from users
where
realname like #{map_realname}
order by userid
limit #{map_offset},#{map_size}
</select>
1.4 多输入参数
MyBatis中允许有多个输入参数,可使用@Param注解来表示
/**
* @Param注解可以实现多个输入参数
* 每个输入参数前必须使用@Param注解
* 注解中value属性的值为获取此参数值的key
*
* 多参数使用了注解之后,mybatis就将这些注解中value属性值与参数值存放一个map集合中
* 其中value属性值为map集合的key
* 参数值为map集合value
*
* 使用此种方法可以省略paramterType的配置
*/
public Users login(@Param(value = "uname") String username, @Param("pwd") String password);
这种做法类似与Map类型的输入参数,其中@Param注解的value属性值为Map的key,在映射文件中通过ognl可获取对应的value,并且parameterType可以不指定类型
<select id="login" resultType="Users">
select userid,username,password,realname from users
where username=#{uname} and password=#{pwd}
</select>
2. resultType输出映射
2.1 简单类型
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射
public int findUsersCount();
<select id="findUsersCount" resultType="int">
select count(*) from users
</select>
2.2 实体类对象和列表
不管是输出的实体类是单个对象还是一个列表(list中包括实体类对象),在mapper.xml中resultType指定的类型是一样的
在原始Dao的方式中,通过selectOne和selectList方法来区分返回值为单个对象或集合列表,而在mapper代理中,则通过接口中定义的方法返回值来区分
public Users findUsersByUserid(Integer userid);
public List<Users> findUsers();
<select id="findUsersByUserid" parameterType="int" resultType="Users">
select userid,username,password,realname from users where userid=#{userid}
</select>
<select id="findUsers" resultType="Users">
select userid,username,password,realname from users order by userid
</select>
2.3 resultMap
- resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和Sql查询的列名一致方可映射成功
- 如果Sql查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还会将查询结果映射到实体类对象中
- resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和list实现一对一查询和一对多查询
定义resultMap
<!--
resultType : 自动映射
resultMap : 手动映射
属性解析:
type : 对应Java中实体类的类型,可以使用全限定名也是使用类型别名
id : <resultMap>标签的唯一标识,在<select>的resultMap属性中使用
子标签:
<id /> 配置对应表中的主键
<result /> 配置对应表中的普通字段
property : 实体类中的属性名
column : 表或结果集中的字段名
javaType : 属性类型
jdbcType : 字段类型
其中javaType与jdbcType可不配置由mybaits自动识别
-->
<resultMap id="usersResultMap" type="Users">
<id property="userid" column="id"/>
<result property="username" column="uname"/>
<result property="password" column="pwd"/>
<result property="realname" column="rname"/>
</resultMap>
使用resultMap作为statement的输出映射类型
<select id="findUsersWithResultMap" resultMap="usersResultMap">
select userid id,username uname,password pwd,realname rname from users
</select>
3. 动态Sql
3.1 什么是动态Sql
动态Sql是指MyBatis核心对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装
3.2 if标签
将实体类不为空的属性作为where条件
<select id="findEmpUseIf" parameterType="map" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where 1=1
<!--test:判断条件,条件成立if标签中的Sql语句拼接-->
<if test="ename!=null and ename!=''">
and ename like #{ename}
</if>
<if test="sal!=null and sal!=0.0">
and sal=#{sal}
</if>
<if test="deptno!=null and deptno!=0">
and deptno=#{deptno}
</if>
</select>
3.2 where标签
where标签中包含的任意if条件如果成立,它就插入一个where关键字。此外,如果标签返回的内容是以AND 或OR 开头的,则它会自动剔除掉
<select id="findEmpUseWhere" parameterType="map" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
<where>
<if test="ename!=null and ename!=''">
and ename like #{ename}
</if>
<if test="sal!=null and sal!=0.0">
and sal=#{sal}
</if>
<if test="deptno!=null and deptno!=0">
and deptno=#{deptno}
</if>
</where>
</select>
3.3 set标签
当在update语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置SET关键字,和剔除追加到条件末尾的任何不相关的逗号
注意:如果set包含的内容为空的话则会出错
<update id="updateEmpUseSet" parameterType="Emp">
update emp
<set>
<if test="ename!=null">
ename=#{ename},
</if>
<if test="job!=null">
job=#{job},
</if>
<if test="mgr!=null">
mgr=#{mgr},
</if>
<if test="sal!=null">
sal=#{sal},
</if>
<if test="comm!=null">
comm=#{comm},
</if>
<if test="hiredate!=null">
hiredate=#{hiredate},
</if>
</set>
where empno=#{empno}
</update>
3.4 trim标签
trim标签属性解析:
- prefix:前缀,包含内容前加上某些字符
- suffix:后缀,包含内容后加上某些字符
- prefixOverrides:剔除包含内容前的某些字符
- suffixOverrides:剔除包含内容后的某些字符
trim来代替where标签的功能
<select id="findEmpUseTrim" parameterType="Emp" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
<trim prefix="where" prefixOverrides="and|or">
<if test="ename!=null">
and ename like #{ename}
</if>
<if test="sal!=null and sal!=0.0">
and sal=#{sal}
</if>
<if test="job!=null">
and job=#{job}
</if>
</trim>
</select>
trim来代替set标签的功能
<update id="updateEmpUseTrim" parameterType="Emp">
update emp
<trim prefix="set" suffixOverrides=",">
<if test="ename!=null">
ename=#{ename},
</if>
<if test="job!=null">
job=#{job},
</if>
<if test="mgr!=null">
mgr=#{mgr},
</if>
<if test="sal!=null">
sal=#{sal},
</if>
<if test="comm!=null">
comm=#{comm},
</if>
<if test="hiredate!=null">
hiredate=#{hiredate},
</if>
</trim>
where empno=#{empno}
</update>
trim实现insert语句
<insert id="insertEmpUseTrim" parameterType="Emp">
insert into emp
<trim suffix="(" prefix=")" prefixOverrides=",">
<if test="ename!=null">
ename,
</if>
<if test="job!=null">
job,
</if>
<if test="mgr!=null">
mgr,
</if>
<if test="hiredate!=null">
hiredate,
</if>
<if test="sal!=null">
sal,
</if>
<if test="comm!=null">
comm,
</if>
</trim>
<trim prefix=" values(" suffix=")" suffixOverrides=",">
<if test="ename!=null">
#{ename},
</if>
<if test="job!=null">
#{job},
</if>
<if test="mgr!=null">
#{mgr},
</if>
<if test="hiredate!=null">
#{hiredate},
</if>
<if test="sal!=null">
#{sal},
</if>
<if test="comm!=null">
#{comm},
</if>
</trim>
</insert>
3.5 foreach标签
- 向Sql传递数组或list,MyBatis使用foreach解析
- Sql只接收一个数组参数,这时sql解析参数的名称MyBatis固定为array
- Sql只接收一个List参数,这时sql解析参数的名称MyBatis固定为list
- 如果是通过一个实体类或pojo的属性传递到Sql的数组或list,则参数的名称为实体类或pojo中的属性名
属性解析:
- index:为数组的下标
- item:每个遍历生成对象中
- open:开始遍历时拼接的串
- close:结束遍历时拼接的串
- separator:遍历的两个对象中需要拼接的串
public void deleteEmpUseForeachArray(int[] empnos);
public List<Emp> findEmpUseForeachList(List<Integer> empnos);
public List<Emp> findEmpUseForeachPojo(EmpQuery query);
<delete id="deleteEmpUseForeachArray" parameterType="int">
delete from emp where empno in
<foreach collection="array" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
<select id="findEmpUseForeachList" parameterType="int" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where empno in
<foreach collection="list" open="(" close=")" separator="," item="empno">
#{empno}
</foreach>
</select>
<select id="findEmpUseForeachPojo" parameterType="EmpQuery" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where deptno in
<foreach collection="deptnos" open="(" close=")" separator="," item="deptno">
#{deptno}
</foreach>
</select>
3.6 choose,when,otherwise标签
if-else-if判断
<select id="">
select...
<choose>
<when test="">
</when>
<when test="">
</when>
<otherwise>
</otherwise>
</choose>
</select>
3.7 Sql片段
将实现的动态Sql判断代码块抽取出来,组成一个Sql片段,其它的statement中就可以引用Sql片段,方便程序员进行开发
注意:在sql片段中不要包括where标签
<select id="findEmpUseSql" parameterType="map" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
<where>
<!-- 引入sql片段 -->
<include refid="empSql"/>
</where>
</select>
<sql id="empSql">
<if test="ename!=null">
and ename like #{ename}
</if>
<if test="sal!=null and sal!=0.0">
and sal=#{sal}
</if>
<if test="deptno!=null and deptno!=0">
and deptno=#{deptno}
</if>
</sql>
五、关联查询
1. 数据模型分析
表功能介绍
- 用户表:记录用户的基本信息
- 订单表:记录用户所创建的订单(购买商品的订单)
- 订单详情表:记录订单的详细信息即购买商品的信息
- 商品表:记录商品的基本信息
表与表之间的业务关系
- 用户表和订单表:
用户表---->订单表:一个用户可以创建多个订单,一对多关系
订单表---->用户表:一个订单只由一个用户创建,一对一关系 - 订单表和订单详情表:
订单表---->订单详情表:一个订单可以包含多个订单详情,因为一个订单可以购买多个商品,每个商品的购买信息在订单详情表中记录,一对多关系
订单详情表----->订单表:一个订单详情只能包括在一个订单中,一对一关系 - 订单详情表和商品表:
订单详情表---->商品表:一个订单详情只对应一个商品信息,一对一关系
商品表---->订单详情表:一个商品可以包括在多个订单详情,一对多关系 - 订单表和商品表:
订单表<---->商品表:一个订单中包含多个商品,一个商品可以添加在多个订单中,两者是通过订单详情表建立关系,多对多关系
2. 一对一查询
2.1 resultType
2.1.1 Sql语句
SELECT
id, order_number,totalprice,status,u.userid,username,password,realname
FROM
orders o,users u
WHERE
o.userid=u.userid
2.1.2 实体类
public class Users {
private Integer userid;
private String username;
private String password;
private String realname;
//get,set方法省略...
}
public class Orders {
private Integer id;
private String order_number;
private Double totalPrice;
private String status;
//get,set方法省略...
}
原始的Orders类不能映射全部字段,需要新创建的实体类,创建一个包括查询字段较多的实体类
OrdersUsers中包含了Orders以及Users需要查询的属性
public class OrdersUsers {
/**
* 对应orders表中的字段
*/
private Integer id;
private String order_number;
private Double totalPrice;
private String status;
/**
* 对象users表中的字段
*/
private Integer userid;
private String username;
private String password;
private String realname;
}
2.1.3 Mapper接口
public List<OrdersUsers> findOrdersUseResultType();
2.1.4 mapper.xml
<select id="findOrdersUseResultType" resultType="OrdersUsers">
SELECT
id, order_number,totalprice,status,u.userid,username,password,realname
FROM
orders o,users u
WHERE
o.userid=u.userid
</select>
2.1.5 测试
SqlSession session = MybatisUtil.openSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
List<OrdersUsers> list = mapper.findOrdersUseResultType();
for (OrdersUsers ou : list) {
System.out.println(ou);
}
session.close();
2.2 resultMap
2.2.1 Sql语句
SELECT
id, order_number,totalprice,status,u.userid,username,password,realname
FROM
orders o,users u
WHERE
o.userid=u.userid
2.2.2 实体类
在Orders类中加入Users属性,Users属性用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个Users对象存储关联查询的用户信息
public class Users {
private Integer userid;
private String username;
private String password;
private String realname;
//get,set方法省略...
}
public class Orders {
private Integer id;
private String order_number;
private Double totalPrice;
private String status;
/**
* 一对一/多对一依赖关系属性
*/
private Users users;
//get,set方法省略...
}
2.2.3 Mapper接口
public List<Orders> findOrdersUseResultMap();
2.2.4 mapper.xml
<resultMap id="ordersUsersResultMap" type="Orders">
<id column="id" property="id"/>
<result column="order_number" property="order_number"/>
<result column="totalprice" property="totalPrice"/>
<result column="status" property="status"/>
<association property="users" javaType="Users">
<id column="userid" property="userid"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="realname" property="realname"/>
</association>
</resultMap>
<select id="findOrdersUseResultMap" resultMap="ordersUsersResultMap">
SELECT
id, order_number,totalprice,status,u.userid,username,password,realname
FROM
orders o,users u
WHERE
o.userid=u.userid
</select>
2.2.5 测试
SqlSession session = MybatisUtil.openSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
List<Orders> list = mapper.findOrdersUseResultMap();
for (Orders o : list) {
System.out.println(o);
}
session.close();
2.3 resultType和resultMap实现一对一查询小结
- resultType:使用resultType实现较为简单,如果实体类中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果查询结果没有特殊要求,建议使用resultType
- resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射到实体类的属性中
- resultMap可以实现延迟加载,resultType无法实现延迟加载
3. 一对多查询
3.1 Sql语句
SELECT
o.id,order_number,totalprice,status,d.id detailid,amount,goods_id
FROM
orders o,ordersdetail d
WHERE
o.id=d.orders_id
3.2 实体类
在Order类中加入List details属性,details属性用于存储关联查询的订单详情,因为订单关联查询订单详情是一对多关系,所以这里使用集合对象存储关联查询的订单详情信息
public class OrdersDetail {
private Integer id;
private Integer amount;
private Integer goods_id;
//get,set方法省略...
}
public class Orders {
private Integer id;
private String order_number;
private Double totalPrice;
private String status;
/**
* 一对多,多对多依赖关系属性
*/
private List<OrdersDetail> details;
//get,set方法省略...
}
3.2 Mapper接口
public List<Orders> findOrdersAndOrdersDetail();
3.3 mapper.xml
<resultMap id="ordersAndDetailResultMap" type="Orders">
<id column="id" property="id"/>
<result column="order_number" property="order_number"/>
<result column="totalprice" property="totalPrice"/>
<result column="status" property="status"/>
<!--
映射依赖关系属性
<collection>用于描述一对多或多对多关系属性
property : 关系属性的名称
OrdersDetail : 关系属性集合中存放的实体类类型
-->
<collection property="details" ofType="OrdersDetail">
<id column="detailid" property="id"/>
<result column="amount" property="amount"/>
<result column="goods_id" property="goods_id"/>
</collection>
</resultMap>
<select id="findOrdersAndOrdersDetail" resultMap="ordersAndDetailResultMap">
SELECT
o.id,order_number,totalprice,status,d.id detailid,amount,goods_id
FROM
orders o,ordersdetail d
WHERE
o.id=d.orders_id
</select>
3.4 测试
SqlSession session = MybatisUtil.openSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
List<Orders> list = mapper.findOrdersAndOrdersDetail();
for (Orders orders : list) {
System.out.println(orders);
}
session.close();
4. 多对多查询
4.1 订单与商品之间的多对多关系
4.1.1 Sql语句
SELECT
o.id,order_number,totalprice,status,d.id detailid,amount,goods_id,gname,descr,price
FROM
orders o,ordersdetail d,goods g
WHERE
o.id=d.orders_id
AND
d.goods_id=g.id
4.1.2 实体类
将Orderdetail类中Integer goods_id属性修改为Goods goods属性,goods属性用于存储关联查询的商品信息。订单与订单详情是一对多关系,订单详情与商品是一对一关系,反之商品与订单详情是一对多关系,订单详情与订单是一对一关系,所以订单与商品之前为多对多关系
public class Goods {
private Integer id;
private String gname;
private String descr;
private Double price;
//get,set方法省略...
}
public class OrdersDetail {
private Integer id;
private Integer amount;
/**
* 一对一依赖关系属性
*/
private Goods goods;
//get,set方法省略...
}
public class Orders {
private Integer id;
private String order_number;
private Double totalPrice;
private String status;
/**
* 一对多,多对多依赖关系属性
*/
private List<OrdersDetail> details;
//get,set方法省略...
}
4.1.3 Mapper接口
public List<Orders> findOrdersAndGoods();
4.1.4 mapper.xml
<resultMap id="ordersAndGoodsResultMap" type="Orders">
<id column="id" property="id"/>
<result column="order_number" property="order_number"/>
<result column="totalprice" property="totalPrice"/>
<result column="status" property="status"/>
<collection property="details" ofType="OrdersDetail">
<id column="detailid" property="id"/>
<result column="amount" property="amount"/>
<association property="goods" javaType="Goods">
<id column="goods_id" property="id"/>
<result column="gname" property="gname"/>
<result column="descr" property="descr"/>
<result column="price" property="price"/>
</association>
</collection>
</resultMap>
<select id="findOrdersAndGoods" resultMap="ordersAndGoodsResultMap">
SELECT
o.id,order_number,totalprice,status,d.id detailid,amount,goods_id,gname,descr,price
FROM
orders o,ordersdetail d,goods g
WHERE
o.id=d.orders_id
AND
d.goods_id=g.id
</select>
4.1.5 测试
SqlSession session = MybatisUtil.openSession();
OrderMapper mapper = session.getMapper(OrderMapper.class);
List<Orders> list = mapper.findOrdersAndGoods();
for (Orders orders : list) {
System.out.println(orders);
}
session.close();
4.2 学生与课程之间的多对多关系
4.2.1 Sql语句
SELECT
s.id,name,gender,major,course_id,cname
FROM
student s,student_course sc,course c
WHERE
s.id=sc.student_id
AND
sc.course_id=c.id
4.2.2 实体类
public class Course {
private Integer id;
private String cname;
//get,set方法省略...
}
public class Student {
private Integer id;
private String name;
private String gender;
private String major;
/**
* 多对多的依赖关系属性
*/
private List<Course> courses;
//get,set方法省略...
}
4.2.3 Mapper接口
public List<Student> findStudent();
4.2.4 mapper.xml
<resultMap id="studentAndCourseResultMap" type="Student">
<id column="id" property="id"/>
<result column="NAME" property="name"/>
<result column="gender" property="gender"/>
<result column="major" property="major"/>
<collection property="courses" ofType="Course">
<id column="course_id" property="id"/>
<result column="cname" property="cname"/>
</collection>
</resultMap>
<select id="findStudent" resultMap="studentAndCourseResultMap">
SELECT
s.id,name,gender,major,course_id,cname
FROM
student s,student_course sc,course c
WHERE
s.id=sc.student_id
AND
sc.course_id=c.id
</select>
4.2.5 测试
SqlSession session = MybatisUtil.openSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> list = mapper.findStudent();
for (Student student : list) {
System.out.println(student);
}
session.close();
5. 关联查询总结
5.1 resultType
作用:将查询结果按照Sql列名与实体类属性名一致性映射到实体类对象中
场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到实体类中,在前端页面遍历list(list中是实体类)即可
5.2 resultMap
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)
5.2.1 association
作用:将关联查询信息映射到一个实体类对象中
场合:为了方便查询关联信息可以使用association将关联信息映射为当前对象的一个属性,比如:查询订单以及关联用户信息
5.2.2 collection
作用:将关联查询信息映射到一个list集合中
场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果映射到list集合中
5.2.3 resultMap的继承
resultMap标签可以通过extends属性来继承一个已有的或公共的resultMap,避免重复配置的出现,减少配置量。例子如下:
<!-- 父resultMap标签-->
<resultMap id="ordersRusultMap" type="Orders">
<id column="id" property="id"/>
<result column="order_number" property="order_number"/>
<result column="totalprice" property="totalPrice"/>
<result column="status" property="status"/>
</resultMap>
<!-- 继承父resultMap标签中的配置,避免重复配置 -->
<resultMap id="ordersUsersResultMap" type="Orders" extends="ordersRusultMap">
<association property="users" javaType="Users">
<id column="userid" property="userid"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="realname" property="realname"/>
</association>
</resultMap>
六、延迟加载
1. 什么是延迟加载
- 需要查询关联信息时,使用MyBatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息等用户获取时再加载
- resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能
2. 打开延迟加载开关
在MyBatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载 | true \ false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 | true \ false | true |
<!-- 全局参数设置 -->
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 使用association实现延迟加载
查询员工以及相关联的部门信息
3.1 Mapper接口
public List<Emp> findEmp();
3.2 mapper.xml
查询员工信息
<select id="findEmp" resultMap="empResultMap">
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
</select>
查询关联的部门信息
通过上边查询到的员工信息中deptno外键去关联查询部门信息,所以需要定义一个根据ID查询部门的语句
<select id="findDeptByDeptno" parameterType="int" resultType="Dept">
select deptno,dname,loc from dept where deptno=#{deptno}
</select>
resultMap配置
<resultMap id="empResultMap" type="Emp">
<id column="empno" property="empno"/>
<result column="ename" property="ename"/>
<result column="job" property="job"/>
<result column="mgr" property="mgr"/>
<result column="hiredate" property="hiredate"/>
<result column="sal" property="sal"/>
<result column="comm" property="comm"/>
<!-- 关系属性描述-->
<!--
<association>实现延迟加载
select : 执行延迟加载时关联数据查询的sql对应的statementId
1.执行的关联查询语句在同一mapper文件中,可以直接使用statementId
2.执行的关联查询语句在不同的mapper文件中,namespace.statementId
column : 在执行关联查询时,使用到主表的一个字段名称(外键字段的名称)
-->
<association property="dept" javaType="Dept" select="com.mybatis.mapper.DeptMapper.findDeptByDeptno" column="deptno">
</association>
</resultMap>
4. 使用collection实现延迟加载
4.1 Mapper接口
public List<Dept> findDept();
4.2 mapper.xml
查询部门信息
<select id="findDept" resultMap="deptResultMap">
select deptno,dname,loc from dept
</select>
查询关联的员工详情信息
通过查询到的部门信息中deptno去关联查询员工详情,所以需要定义一个根据部门ID查询员工详情的语句
<select id="findEmpByDeptno" parameterType="int" resultType="Emp">
select empno,ename,job,mgr,hiredate,sal,comm from emp
where deptno=#{deptno}
</select>
resultMap配置
<resultMap id="deptResultMap" type="Dept">
<id column="deptno" property="deptno"/>
<result column="dname" property="dname"/>
<result column="loc" property="loc"/>
<collection property="empList" ofType="Emp" select="com.mybatis.mapper.EmpMapper.findEmpByDeptno" column="deptno"/>
</resultMap>