一文教你玩转Mybatis,超详细代码讲解与实战
一、Mybatis 入门
1.1 什么是MyBatis
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。可以简单理解为框架是一个半成品的项目,我们可以基于框架进行项目开发,提高开发效率。现在互联网企业都是效率至上,如果开发周期过长,可能市场很快就会出现同类产品,失去先机。
当前,最新版本是MyBatis 3.5.9,其发布时间是2021年12月26日。
MyBatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等JDBC繁杂的过程代码。
MyBatis通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 Java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由mybatis框架执行sql并将结果映射成Java对象并返回。
1.2 MyBatis的优点
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
提供映射标签,支持对象与数据库的orm字段关系映射。
提供对象关系映射标签,支持对象关系组建维护。
提供xml标签,支持编写动态sql。
1.3 JDBC编程存在的问题
在实际开发过程中,我们一般使用ORM框架来代替传统的JDBC,例如Mybatis或者Hibernate,但JDBC是Java用来实现数据访问的基础,掌握它对于我们理解Java的数据操作流程很有帮助。
ORM:对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
Java典型的ORM中间件有:Hibernate,Mybatis,speedframework。
ORM技术特点:
1、提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。
2、ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。
那为什么我们在实际开发中都是用一些ORM框架而不用传统的JDBC进行开发呢?接下来我们来看一下一个JDBC程序的简单实现并分析其缺点,如下:
1.3.1 JDBC程序
@Overridepublic void insertUser(User user) { Connection conn = DBUtil.getConnection(); PreparedStatement stmt = null; String sql = "insert into sys_user(NAME,ACCT,PWD,CRTIME,UPTIME) values(?,?,?,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.3.2 JDBC编程步骤
1、加载数据库驱动;
2、创建并获取数据库连接Connection;
3、创建执行SQL语句的PreparedStatement对象;
4、设置SQL语句中的占位符参数;
5、通过PreparedStatement执行Sql并获取结果集;
6、对Sql执行结果进行解析处理;
7、释放资源(Connection、Preparedstatement、ResultSet)。
1.3.3 JDBC问题总结如下
从以上的程序中,进行总结如下:
1、数据库连接使用时就创建,不使用时便立即释放,从而对数据库进行频繁的操作,导致资源的浪费、影响性能;
优化设想:使用数据库连接池管理数据库对象;
2、sql都是硬编码到Java程序中,如果改变sql,那么得重新编译Java代码,不利于系统后期的维护;
优化设想:将sql语句配置在xml中,即使改变sql也不用重新编译源代码;
3、向PreparedStatement设置参数,也是硬编码到Java程序中,不利于后期的维护;
优化设想:将sql语句以及占位符和参数全部配置在xml中,改动也不需要重新编译源代码;
4、从resultset遍历结果集数据时,也存在硬编码,不利于后期系统的维护;
优化设想:将查询的结果集自动映射成Java对象;
针对以上问题,顺其自然的就出现了许多优化JDBC的方案,也就是后期出现的ORM持久层框架,例如Mybatis以及Hibernate等等;这也就是为什么在实际开发中都比较喜欢用ORM框架的原因了。
有了这个概念,那么接下来我们就开始对Mybatis进行学习...
注:硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。硬编码数据通常只能通过编辑源代码和重新编译可执行文件来修改。
1.4 MyBatis架构
1、MyBatis配置:
mybatis-config.xml(名称不固定),此文件作为MyBatis的全局(核心)配置文件,配置了MyBatis的运行环境等信息。
mapper.xml文件即Sql映射文件,文件中配置了操作数据库的Sql语句。此文件需要在mybatis-config.xml中加载。
2、通过MyBatis环境等配置信息构造SqlSessionFactory,即会话工厂。
3、由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行。
4、MyBatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、MappedStatement也是MyBatis一个底层封装对象,Mybatis将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。mapper.xml文件中一个Sql对应一个MappedStatement对象,Sql的id即是MappedStatement的id。
6、MappedStatement对Sql执行输入参数进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor通过MappedStatement在执行Sql前将输入的Java对象映射至Sql中,输入参数映射就是JDBC编程中对PreparedStatement设置参数。
7、MappedStatement对Sql执行输出结果进行定义,包括HashMap、基本类型、字符串类型、实体类类型,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射过程相当于JDBC编程中对结果的解析处理过程。
原理分析:
1、加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
2、SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
3、SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
4、结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
1.5 搭建MyBatis项目
1.5.1 建立Java项目
MyBatis是一个持久层框架,是操作数据库时使用的。无需创建JavaWEB项目,建立Maven Java项目即可。
1.5.2 导入MyBatis框架jar包
Mybaits的代码由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases
mybatis目录结构:
lib -> mybatis附属工具包
LICENSE -> 许可证
mybatis-3.4.6.jar -> mybatis的核心包
mybatis-3.4.6.pdf -> mybatis使用手册
NOTICE -> 法律声明
方式一:导入jar包
mybatis-3.4.6.jar为核心jar包,必须引入。
lib目录下的jar包为工具包,可有可无,我们一般会引入log4j作为mybatis日志输出组件。
mysql或oracle数据库的驱动包,必须引入。
方式二:maven依赖配置
org.mybatis mybatis 3.4.6 com.oracle ojdbc6 11.2.0.1.0 mysql mysql-connector-java 5.1.49 log4j log4j 1.2.17 junit junit 4.12 test
oracle依赖报错解决:https://blog.csdn.net/qq_38000902/article/details/82759268
在oracle安装目录里面就有ojdbc6.jar:D:\Tools\product\11.2.0\dbhome_1\jdbc\lib(我的路径)
mybatis默认使用log4j作为输出日志信息,你也可以引入log4j相关文件。
1.5.3 编写MyBatis中全局配置文件
在src/main/resources目录下创建mybatis-config.xml,作用:配置了数据源、事务等MyBatis运行环境等。
注:如果src/main下面没有resources目录,那么我们手动创建一个,让其成为Resources Root。
MyBatis配置文件报错:
在编写xml配置文件引入DTD约束时,可能会出现这个错误提示 URI is not registered(Settings | Languages & Frameworks | Schemas and DTDs),此时使用快捷键,选择Fetch external resource 或 Ignore external resource选项。
或者直接在设置settings -> languages&frameworks -> schemas and dtds 中添加出问题的路径。
1.5.4 数据库SQL
CREATE TABLE `dept` ( `deptno` int PRIMARY KEY AUTO_INCREMENT, `dname` varchar(20), `loc` varchar(40));INSERT INTO `dept` VALUES (10, 'ACCOUNTING', 'NEW YORK');INSERT INTO `dept` VALUES (20, 'RESEARCH', 'DALLAS');INSERT INTO `dept` VALUES (30, 'SALES', 'CHICAGO');INSERT INTO `dept` VALUES (40, 'OPERATIONS', 'BOSTON');CREATE TABLE `emp` ( `empno` int PRIMARY KEY AUTO_INCREMENT, `ename` varchar(20), `job` varchar(20), `mgr` int, `hiredate` date, `sal` double, `comm` double, `deptno` int, CONSTRAINT `FK_EMP_DEPTNO` FOREIGN KEY (`deptno`) REFERENCES `dept` (`deptno`));INSERT INTO `emp` VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 1300, NULL, 20);INSERT INTO `emp` VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 2100, 300, 30);INSERT INTO `emp` VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1750, 500, 30);INSERT INTO `emp` VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 3475, NULL, 20);INSERT INTO `emp` VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1750, 1400, 30);INSERT INTO `emp` VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 3350, NULL, 30);INSERT INTO `emp` VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2950, NULL, 10);INSERT INTO `emp` VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3500, NULL, 20);INSERT INTO `emp` VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5500, NULL, 10);INSERT INTO `emp` VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 2000, 0, 30);INSERT INTO `emp` VALUES (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1600, NULL, 20);INSERT INTO `emp` VALUES (7900, 'JAMES', 'CLERK', 7698, '0198-12-31', 1450, NULL, 30);INSERT INTO `emp` VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3500, NULL, 20);INSERT INTO `emp` VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1800, NULL, 10);
1.5.5 编写实体类
实体类作为Mybatis进行sql映射使用,实体类通常与数据库表对应,Emp.java如下:
注:实体类是用来和数据库表对应的,我们最好全部使用引用类型。
package com.newcapec.entity;import java.util.Date;public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + '}'; }}
1.5.6 编写映射文件
在src/main/resources下创建mapper目录,在该目录下创建sql映射文件Emp.xml。
<!-- 通过标签编写查询语句 id: 映射文件中SQL语句的唯一标识 mybatis会将SQL语句封装到MappedStatement对象中,所以此处的id也可以标识MappedStatement对象的id 注意:同一个mapper文件中id不能重复,而且id在mapper代理模式下有着重要作用 parameterType: 输入参数的类型 sql语句的占位符:#{} #{empno}:其中empno表示接收输入的参数值,参数名称为empno但是如果参数的类型为简单类型(基本数据类型、包装类、字符串类型),参数名称可以任意指定resultType: 输出参数的类型需要指定输出数据为Java中的数据类型(实体类的全限定名) --> select empno,ename,job,hiredate,mgr,sal,comm,deptno from emp where empno=#{empno}
1.5.7 加载映射文件
在MyBatis的全局配置文件中添加映射文件位置。
1.5.8 log4j配置
Mybatis日志输出:log4j.properties配置文件。
#井号表示注释,配置内容为键值对格式,每行只能有一个键值对,键值对之间以=连接#指定logger#设定log4j的日志级别和输出的目的地#INFO日志级别 ,Console和logfile输出的目的地#等级 OFF,ERROR,WARN,INFO,DEBUG,TRACE,ALLlog4j.rootLogger=DEBUG,Console#指定appender#设定Logger的Console,其中Console为自定义名称,类型为控制台输出log4j.appender.Console=org.apache.log4j.ConsoleAppender#设定Logger的logfile,其中logfile为自定义名称,类型为文件#org.apache.log4j.FileAppender文件#org.apache.log4j.RollingFileAppender文件大小到达指定尺寸后产生一个新的文件#org.apache.log4j.DailyRollingFileAppender每天产生一个日志文件log4j.appender.logfile=org.apache.log4j.RollingFileAppender#设定文件的输出路径log4j.appender.logfile.File=d:/log/test.log#设定文件最大尺寸 单位可以使KB,MB,GBlog4j.appender.logfile.MaxFileSize=2048KB#输出格式#设定appender布局Layout# %d 输出日志的日期和时间,指定格式:%d{yyyy-MM-dd HH:mm:ss SSS}# %p 输出的日志级别# %c 输出所属类的全类名# %M 方法名# %m 输出代码中指定消息# %n 一个换行符log4j.appender.Console.layout=org.apache.log4j.PatternLayoutlog4j.appender.Console.layout.ConversionPattern=%d %p %c.%M() --%m%nlog4j.appender.logfile.layout=org.apache.log4j.PatternLayoutlog4j.appender.logfile.layout.ConversionPattern=%d %p %c.%M() --%m%n
1.5.9 编写测试程序
推荐使用Junit单元测试。
package com.newcapec;import com.newcapec.entity.Emp;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;import java.io.IOException;import java.io.InputStream;/** * 测试程序 */public class MybatisTest { @Test public void test() throws IOException { //1.创建读取全局配置文件的流 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //2.通过配置文件流创建会话工厂 SqlSessionFactory factory = builder.build(in); //3.通过会话工厂创建会话对象(SqlSession) SqlSession session = factory.openSession(); //4.通过会话对象操作数据 /** * 查询单条记录 * selectOne(String statementId, Object param) * 参数1:映射文件中的statementId,命名空间名.statementId * 参数2:向sql语句中传入的数据,注意:传入的数据类型必须与映射文件中配置的parameterType保持一致 * 返回值:就是映射文件中配置的resultType的类型 * 查询多条记录 * selectList() */ Emp emp = session.selectOne("emp.selectById", 7369); System.out.println(emp); //5.关闭资源 session.close(); }}
1.6 增删改查的基本操作
实现以下功能:
查询所有员工信息
添加员工
更新员工
删除员工
根据员工名模糊查询
1.6.1 查询操作
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp order by empno desc
Java代码:
public class CURDTest { @Test public void testSelect() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); /** * 查询多条记录 * selectList(statement-sql语句的id, parameter-参数) * 表示将单条记录都存储输出映射对象中,每条记录的映射对象存放在List集合中 */ List list = session.selectList("emp.select"); for (Emp emp : list) { System.out.println(emp); } session.close(); }}
1.6.2 新增操作
mapper文件:
insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
Java代码:
@Testpublic void testInsert() throws IOException { 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("CLARK"); emp.setMgr(1); emp.setHiredate(new Date()); emp.setSal(6500.0); emp.setComm(1200.0); System.out.println("新增之前的主键值为:" + emp.getEmpno()); int result = session.insert("emp.insert", emp); System.out.println("影响数据库的条数为:" + result); /** * mybatis中的事务是jdbc的事务机制,mybatis里面默认是手动提交 */ session.commit(); System.out.println("新增之后的主键值为:" + emp.getEmpno()); session.close();}
插入数据的主键返回:
-
select last_insert_id(),表示得到刚insert进去记录的主键值,适用与自增主键的数据库;
-
select seq_demo.nextval from dual,表示获取下一个序列生成的值,适用于存在序列的数据库;
-
keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性;
-
order:selectKey标签中Sql语句,相对于insert语句来说的执行顺序;
-
resultType:指定selectKey标签中Sql语句的结果类型;
Oracle数据库:
select seq_demo.nextval from dual insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values(#{empno},#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
MySQL数据库:
select last_insert_id() insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
如果是主键自增型数据库(MySQL)还可以使用:
-
useGeneratedKeys: 是否开启主键返回,默认不开启false;
-
keyProperty: 获取到主键值后存放在实体类哪个成员变量中;
insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
1.6.3 修改操作
mapper文件:
update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno} where empno=#{empno}
Java代码:
@Testpublic void testUpdate() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); Emp emp = new Emp(); emp.setEmpno(7936); emp.setEname("JERRY"); emp.setJob("MANAGER"); emp.setMgr(7698); emp.setHiredate(new Date(new Date().getTime() + 1000*60*60*24)); emp.setSal(7800.0); emp.setComm(800.0); int result = session.update("emp.update", emp); System.out.println("影响数据库的条数为:" + result); session.commit(); session.close();}
1.6.4 删除操作
mapper文件:
delete from emp where empno=#{empno}
Java代码:
@Testpublic void testDelete() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); int result = session.delete("emp.delete", 7935); System.out.println("影响数据库的条数为:" + result); session.commit(); session.close();}
1.6.5 模糊查询
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like #{ename}
Java代码:
@Testpublic void testSelectByEname() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); String ename = "S"; List list = session.selectList("emp.selectByEname1", "%"+ename+"%"); for (Emp emp : list) { System.out.println(emp); } session.close();}
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like concat('%',#{ename},'%')
Java代码:
@Testpublic void testSelectByEname2() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); String ename = "S"; List list = session.selectList("emp.selectByEname2", ename); for (Emp emp : list) { System.out.println(emp); } session.close();}
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like '%${value}%'
Java代码:
@Testpublic void testSelectByEname3() throws IOException { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession session = factory.openSession(); String ename = "S"; List list = session.selectList("emp.selectByEname3", ename); for (Emp emp : list) { System.out.println(emp); } session.close();}
1.6.6 总结
parameterType和resultType:
parameterType:指定输入参数类型,MyBatis通过ognl从输入对象中获取参数值设置在Sql中;
resultType:指定输出结果类型,MyBatis将Sql查询结果的一行记录数据映射为resultType指定类型的对象;
#{} 和 ${}:
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型、实体类类型、HashMap;
#{}接收简单类型,#{}中可以写成value或其它名称;
#{}接收实体类对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值;
${}表示一个拼接符号,会引用Sql注入,所以不建议使用${};
${}接收输入参数,类型可以是简单类型、实体类类型、HashMap;
${}接收简单类型,${}中只能写成value;
${}接收实体类对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性...的方式获取对象属性值;
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
1.7 MyBatis和Hibernate区别
Hibernate:是一个标准ORM框架(对象关系映射)。
入门门槛较高的,不需要程序写Sql,Sql语句自动生成,对sql语句进行优化、修改比较困难的;
应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,erp、crm、oa...;
MyBatis:专注于Sql本身,需要程序员自己编写Sql语句,Sql修改、优化比较方便;
MyBatis是一个不完全的ORM框架,虽然程序员自己写Sql,MyBatis也可以实现映射(输入映射、输出映射);
应用场景:适用与需求变化较多的项目,比如:互联网项目
企业进行技术选型,以低成本高回报作为技术选型的原则,根据项目组的技术力量进行选择。
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照员工的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
1.8 Mybatis解决JDBC编程的问题
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库链接。
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
二、Mybatis DAO开发
使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法。
2.1 Mybatis API
2.1.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
2.1.2 SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
注:openSession(true),true为自动提交事务,false则相反。
2.1.3 SqlSession
SqlSession是一个面向用户(程序员)的接口,其中提供了很多操作数据库的方法。如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、insert、update、delete。
SqlSession的实例不能共享使用,它是线程不安全的,每个线程都应该有它自己的SqlSession实例,因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
2.2 Mybatis工具类
为了简化MyBatis的开发,可将MyBatis进一步封装。
package com.newcapec.util;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;/** * Mybatis工具类 */public class MybatisUtil { /** * 不让用户在外界创建工具类对象 */ private MybatisUtil() { } /** * 初始化SqlSessionFactory对象 */ 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 getSession() { return factory.openSession(); }}
测试:
public class MybatisUtilTest { @Test public void test() { SqlSession sqlSession = MybatisUtil.getSession(); System.out.println(sqlSession); sqlSession.close(); }}
2.3 原始DAO开发方式
原始Dao开发方法需要程序员编写Dao接口和Dao实现类,无非就是Dao实现类里面调用映射文件里面定义的sql而已。
2.3.1 实体类
package com.newcapec.entity;/** * Dept实体类 */public class Dept { private Integer deptno; private String dname; private String loc; public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; }}
2.3.2 接口
package com.newcapec.dao;import com.newcapec.entity.Dept;import java.util.List;public interface DeptDao { List select(); Dept selectById(Integer deptno); int insert(Dept dept); int update(Dept dept); int delete(Integer deptno);}
2.3.3 实现类
package com.newcapec.dao.impl;import com.newcapec.dao.DeptDao;import com.newcapec.entity.Dept;import com.newcapec.util.MybatisUtil;import org.apache.ibatis.session.SqlSession;import java.util.List;/** * DeptDao实现类 */public class DeptDaoImpl implements DeptDao { @Override public List select() { SqlSession sqlSession = MybatisUtil.getSession(); List list = sqlSession.selectList("dept.select"); sqlSession.close(); return list; } @Override public Dept selectById(Integer deptno) { SqlSession sqlSession = MybatisUtil.getSession(); Dept dept = sqlSession.selectOne("dept.selectById", deptno); sqlSession.close(); return dept; } @Override public int insert(Dept dept) { SqlSession sqlSession = MybatisUtil.getSession(); int result = sqlSession.insert("dept.insert", dept); sqlSession.commit(); sqlSession.close(); return result; } @Override public int update(Dept dept) { SqlSession sqlSession = MybatisUtil.getSession(); int result = sqlSession.update("dept.update", dept); sqlSession.commit(); sqlSession.close(); return result; } @Override public int delete(Integer deptno) { SqlSession sqlSession = MybatisUtil.getSession(); int result = sqlSession.delete("dept.delete", deptno); sqlSession.commit(); sqlSession.close(); return result; }}
2.3.4 mapper文件
select deptno,dname,loc from dept select deptno,dname,loc from dept where deptno=#{deptno} insert into dept(dname,loc) values(#{dname},#{loc}) update dept set dname=#{dname},loc=#{loc} where deptno=#{deptno} delete from dept where deptno=#{deptno}
加载mapper文件:
2.3.5 测试
package com.newcapec;import com.newcapec.dao.DeptDao;import com.newcapec.dao.impl.DeptDaoImpl;import com.newcapec.entity.Dept;import org.junit.Test;import java.util.List;public class DaoTest { private DeptDao deptDao = new DeptDaoImpl(); @Test public void testSelect() { List list = deptDao.select(); for (Dept dept : list) { System.out.println(dept); } } @Test public void testSelectById() { Dept dept = deptDao.selectById(20); System.out.println(dept); } @Test public void testInsert() { Dept dept = new Dept(); dept.setDname("企划部"); dept.setLoc("深圳"); int result = deptDao.insert(dept); System.out.println("影响数据库的条数:" + result); } @Test public void testUpdate() { Dept dept = new Dept(); dept.setDeptno(41); dept.setDname("生产部"); dept.setLoc("杭州"); int result = deptDao.update(dept); System.out.println("影响数据库的条数:" + result); } @Test public void testDelete() { int result = deptDao.delete(41); System.out.println("影响数据库的条数:" + result); }}
2.3.6 原始DAO开发问题
dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
注:原始Dao开发和我们Web阶段讲解的Dao开发基本类似,都是有Dao接口和Dao实现类,无非Web阶段的Dao实现类中通过DBUtils来操作SQL;现在Mybatis的原始Dao开发,把SQL分离出去了,写在的XML映射文件里面而已。
2.4 Mapper代理方式(重点)
Mapper代理开发方式只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
程序员编写Mapper接口需要遵循一些开发规范,MyBatis可以自动生成Mapper接口实现类代理对象。
2.4.1 开发规范
1、Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、Mapper接口方法名和Mapper.xml中定义的每个标签的id相同。
3、Mapper接口方法的参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
4、Mapper接口方法返回值类型和mapper.xml中定义的每个sql的resultType的类型相同。
注:Mapper.xml映射文件最好和Mapper接口名称一致;
2.4.2 实体类
package com.newcapec.entity;import java.util.Date;/** * Emp实体类 */public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + '}'; }}
2.4.3 Mapper接口
package com.newcapec.mapper;import com.newcapec.entity.Emp;import java.util.List;/* * Mapper接口相当于我们之前写的Dao接口,只是在Mybatis里面我们习惯这么写而已。 */public interface EmpMapper { List select(); Emp selectById(Integer empno); void insert(Emp emp); int update(Emp emp); boolean delete(Integer empno);}
-
批量查询:方法返回值为List类型,表示SqlSession对象将调用selectList()方法。
-
单条查询:方法返回值为单个实体对象,表示SqlSession对象将调用selectOne()方法。
-
增删改:
-
方法返回值为void,表示SqlSession对象中insert,update,delete方法的返回值不做任何处理。
-
方法返回值为int类型,表示SqlSession对象中insert,update,delete方法的返回值直接返回。
-
方法返回值为boolean类型,表示根据SqlSession对象中的insert,update,delete方法返回值(影响数据库的条数)判断操作是否成功,如果影响数据库的条数大于0条,表示成功,否则表示失败。
-
2.4.4 mapper文件
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp select empno,ename,job,hiredate,mgr,sal,comm,deptno from emp where empno=#{empno} insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno}) update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno} where empno=#{empno} delete from emp where empno=#{empno}
加载mapper文件:
2.4.5 测试
package com.newcapec;import com.newcapec.entity.Emp;import com.newcapec.mapper.EmpMapper;import com.newcapec.util.MybatisUtil;import org.apache.ibatis.session.SqlSession;import org.junit.After;import org.junit.Before;import org.junit.Test;import java.util.Date;import java.util.List;public class MapperTest { private SqlSession sqlSession; private EmpMapper empMapper; @Before public void before() { sqlSession = MybatisUtil.getSession(); //获取Mapper接口的代理对象 empMapper = sqlSession.getMapper(EmpMapper.class); } @After public void after() { sqlSession.commit(); sqlSession.close(); } @Test public void test() { System.out.println(sqlSession); System.out.println(empMapper); } @Test public void testSelect() { List list = empMapper.select(); for (Emp emp : list) { System.out.println(emp); } } @Test public void testSelectById() { Emp emp = empMapper.selectById(7938); System.out.println(emp); } @Test public void testInsert() { Emp emp = new Emp(); emp.setEname("小明"); emp.setJob("职员"); emp.setSal(4500.0); emp.setComm(1000.0); emp.setMgr(1); emp.setHiredate(new Date()); empMapper.insert(emp); } @Test public void testUpdate() { Emp emp = new Emp(); emp.setEmpno(7940); emp.setEname("小李"); emp.setJob("秘书"); emp.setSal(5300.0); emp.setComm(1300.0); emp.setMgr(1); emp.setHiredate(new Date()); int result = empMapper.update(emp); System.out.println("方法的返回值:" + result); } @Test public void testDelete() { boolean result = empMapper.delete(7940); System.out.println("方法的返回值:" + result); }}
Mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。
三、Mybatis 核心配置文件
MyBatis的全局配置文件mybatis-config.xml,配置内容如下:
-
properties(属性)
-
settings(全局配置参数)
-
typeAliases(类型别名)
-
typeHandlers(类型处理器)
-
objectFactory(对象工厂)
-
plugins(插件)
-
environments(环境集合属性对象)
-
environment(环境子属性对象)
-
transactionManager(事务管理)
-
dataSource(数据源)
-
-
-
mappers(映射器)
3.1 properties属性
将数据库连接参数单独配置在db.properties中,只需要在mybatis-config.xml中加载db.properties的属性值。在mybatis-config.xml中就不需要对数据库连接参数硬编码。
# Oracle相关配置jdbc.oracle.driver=oracle.jdbc.driver.OracleDriverjdbc.oracle.url=jdbc:oracle:thin:@localhost:1521:orcljdbc.oracle.username=scottjdbc.oracle.password=tiger# Mysql相关配置jdbc.mysql.driver=com.mysql.jdbc.Driverjdbc.mysql.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&useSSL=falsejdbc.mysql.username=rootjdbc.mysql.password=root
使用 标签加载属性文件:
-
resource: 从classpath下读取资源文件
-
classpath为maven项目中的src/main/resources目录 + src/main/java目录,编译之后的目录为target/classes;
-
-
url: 从当前系统环境的文件系统中读取资源
-
windows: d:/conf/test.properties
-
linux: /home/tom/conf/test.properties
-
<!---->
在环境中使用:
注意:MyBatis将按照下面的顺序来加载属性
-
在properties标签体内定义的属性首先被读取;
-
然后会读取properties标签中resource或url加载的属性,它会覆盖已读取的同名属性;
-
最后读取parameterType传递的属性,它会覆盖已读取的同名属性;
建议:
-
不要在properties标签体内添加任何属性值,只将属性值定义在properties文件中;
-
在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX;
3.2 settings全局配置参数
MyBatis框架在运行时可以调整一些运行参数,比如:开启二级缓存、开启延迟加载。
可配置参数如下:
设置 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 在全局范围内启用或禁用缓存配置任何映射器在此配置下。 | true, false | true |
lazyLoadingEnabled | 在全局范围内启用或禁用延迟加裁。禁用时,所有将会将热加载。 | true, false | false |
aggressiveLazyLoading | 当开启时,任何方法的调用都会加载该对象的所有属性。 | true, false | false |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要驱动支持)。 | true, false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true, false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。 | true, false | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应;WARNING: 输出提醒日志 ('AutoMappingUnknownColumnBehavior'的日志等级必须设置为 WARN);FAILING: 映射失败 (抛出 SqlSessionException)。 | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE, REUSE, BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true, false | false |
safeResultHandlerEnabled | 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。 | true, false | true |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION, STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL, VARCHAR 或 OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | 用逗号分隔的方法列表 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | 一个类型别名或完全限定类名 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。 | 一个类型别名或完全限定类名 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true, false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。 | true, false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB, JAVASSIST | JAVASSIST |
vfsImpl | 指定 VFS 的实现。 | 自定义 VFS 的实现的类全限定名,以逗号分隔 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。 |
true, false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。 |
类型别名或者全类名 | 未设置 |
开启下划线与驼峰命名的转换:
3.3 typeAliases类型别名
在mapper.xml中,定义很多的statement。statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
3.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.3.2 自定义别名
单个定义别名:
单个类型的别名配置:typeAlias。
type:类型,java中类的全限定名;
alias:别名;
批量定义别名:
批量类型别名的配置:package。
name:需要配置别名的类所在的包;
设置包名之后,此包下所有的类都拥有类型别名,其别名为:该类的类名,首字母大小均可。
<!-- 批量别名配置 name:需要配置别名的实体类所在包的包名 默认别名为该类的类名,其首字母大小均可 -->
注解定义别名:
在实体类上可以使用 @Alias("name") 注解来标识该类的别名。
@Alias("depart")public class Dept { //...}
注意:配置和注解仅能使用一种方式,当注解存在时,则其别名为其注解值。
3.3.3 应用
实体类:
public class Dept { private Integer deptno; private String dname; private String loc; public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; }}
接口:
package com.newcapec.mapper;import com.newcapec.entity.Dept;import java.util.List;public interface DeptMapper { List select(); Dept selectById(Integer deptno); List selectByDname(String dname);}
mapper文件:
select deptno,dname,loc from dept select deptno,dname,loc from dept where deptno=#{deptno} select deptno,dname,loc from dept where dname=#{dname}
测试:
package com.newcapec;import com.newcapec.entity.Dept;import com.newcapec.mapper.DeptMapper;import com.newcapec.util.MybatisUtil;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;/** * 别名测试 */public class TypeAliasTest { @Test public void testSelect() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List list = deptMapper.select(); for (Dept dept : list) { System.out.println(dept); } sqlSession.close(); } @Test public void testSelectById() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.selectById(10); System.out.println(dept); sqlSession.close(); } @Test public void testSelectByDname() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List list = deptMapper.selectByDname("ACCOUNTING"); System.out.println(list); sqlSession.close(); }}
3.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-任何兼容的字符串类型,作为代码存储(而不是索引) |
3.5 mappers映射器
mappers:映射器,加载mapper文件。
3.5.1 单个加载映射文件
单个映射文件的加载:mapper。
-
resource: 从classpath下加载mapper文件
<!-- 单个加载映射文件 resource:通过相对路径加载文件(项目源目录下的文件) url:通过绝对路径加载文件(文件系统中文件) -->
-
class: 配置dao接口的全限定名,通过Java中的dao接口的名称加载mapper.xml文件
<!-- 单个加载映射文件 class : 配置mapper接口的全限定名,通过Java中的Mapper接口来加载映射文件 -->
要求:
-
必须使用mapper代理的开发方式;
-
mapper.xml文件的名称必须与dao接口的名称保持一致;
-
mapper.xml文件必须与dao接口放在同一个目录下;
注意:同一个目录是指编译之后的目录,并非开发时的目录。
3.5.2 批量加载映射文件
批量映射文件的加载:package。
-
name:dao接口与mapper文件存放的共同的目录名称
此种配置使mapper扫描指定包,并在此包下获取所有的接口以及与接口名称相同mapper文件,并加载;
<!-- 批量加载映射文件 name:存放Mapper接口与mapper.xml文件的包名 -->
要求:与mapper接口加载单个映射文件(class方式)一致。
3.5.3 应用
实体类:
public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; private String gender; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + ", gender='" + gender + '\'' + '}'; }}
接口:
public interface EmpMapper { List select();}
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
测试:
/** * Mappers测试 */public class MappersTest { @Test public void testSelectEmp() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = empMapper.select(); for (Emp emp : list) { System.out.println(emp); } sqlSession.close(); }}
3.6 Mybatis配置文件的标签顺序
Mybatis配置文件中各标签的位置顺序如下:
properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers? 具体可以参考 http://mybatis.org/dtd/mybatis-3-config.dtd 文件。
四、Mybatis Mapper配置文件
mapper.xml映射文件中定义了操作数据库的Sql,每个Sql是一个statement,映射文件是MyBatis的核心。
4.1 parameterType输入映射
parameterType配置输入参数的类型。
4.1.1 表结构
CREATE TABLE `users` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `username` varchar(20), `password` varchar(50), `realname` varchar(20));INSERT INTO `users` VALUES (1, 'admin', '123456', '管理员');INSERT INTO `users` VALUES (2, 'tom', '123', '汤姆');INSERT INTO `users` VALUES (3, 'jerry', '456', '杰瑞');INSERT INTO `users` VALUES (4, 'zhangsan', '111', '张三');INSERT INTO `users` VALUES (5, 'lisi', '222', '李四');
4.1.2 实体类
package com.newcapec.entity;public class Users { private Integer id; private String username; private String password; private String realname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } @Override public String toString() { return "Users{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", realname='" + realname + '\'' + '}'; }}
4.1.3 简单类型
Java基本数据类型以及包装类,String字符串类型。
mapper接口:
package com.newcapec.mapper;import com.newcapec.entity.Users;import java.util.List;public interface UsersMapper { List selectByRealname(String realname);}
mapper文件:
select id,username,password,realname from users where realname like concat('%',#{realname},'%')
测试:
package com.newcapec;import com.newcapec.entity.Users;import com.newcapec.mapper.UsersMapper;import com.newcapec.util.MybatisUtil;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class ParameterTypeTest { @Test public void testSimpleParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); List list = usersMapper.selectByRealname("张"); for (Users users : list) { System.out.println(users); } sqlSession.close(); }}
4.1.4 实体类或自定义类型
开发中通过实体类或pojo类型传递查询条件,查询条件是综合的查询条件,不仅包括实体类中查询条件还包括其它的查询条件,这时可以使用包装对象传递输入参数。
- 自定义类型
分页类:
package com.newcapec.entity;/** * 分页类 */public class Page { //当前页码 private Integer pageNum = 1; //每页条数 private Integer pageSize = 3; //总页数: 总记录数/每页条数,除不尽+1 private Integer pages; //总记录数 private Integer total; /** * mysql * 起始偏移量:(当前页码-1)*每页条数 */ private Integer offset; /** * oracle * 起始条数:(当前页码-1)*每页条数+1 * 结束条数: 当前页码*每页条数 */ private Integer start; private Integer end; public Integer getPageNum() { return pageNum; } public void setPageNum(Integer pageNum) { this.pageNum = pageNum; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } public Integer getPages() { return getTotal() % getPageSize() == 0 ? getTotal() / getPageSize() : getTotal() / getPageSize() + 1; } public void setPages(Integer pages) { this.pages = pages; } public Integer getTotal() { return total; } public void setTotal(Integer total) { this.total = total; } public Integer getOffset() { return (getPageNum() - 1) * getPageSize(); } public void setOffset(Integer offset) { this.offset = offset; } public Integer getStart() { return (getPageNum() - 1) * getPageSize() + 1; } public void setStart(Integer start) { this.start = start; } public Integer getEnd() { return getPageNum() * getPageSize(); } public void setEnd(Integer end) { this.end = end; }}
复合类:UsersQuery
package com.newcapec.entity;/** * 多条件查询复合类 */public class UsersQuery { private Users users; private Page page; public Users getUsers() { return users; } public void setUsers(Users users) { this.users = users; } public Page getPage() { return page; } public void setPage(Page page) { this.page = page; }}
- mapper接口
List selectByPage(Page page);List selectByRealnameAndPage(UsersQuery usersQuery);
- mapper文件
select id,username,password,realname from users order by id limit #{offset}, #{pageSize} select id,username,password,realname from users where realname like concat('%',#{users.realname},'%') order by id limit #{page.offset}, #{page.pageSize}
- 测试
@Testpublic void testClassParam1() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Page page = new Page(); page.setPageNum(1); System.out.println("mysql起始偏移量:" + page.getOffset()); System.out.println("起始条数:" + page.getStart()); System.out.println("结束条数:" + page.getEnd()); List list = usersMapper.selectByPage(page); for (Users users : list) { System.out.println(users); } sqlSession.close();}@Testpublic void testPojoParam2() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Page page = new Page(); page.setPageNum(1); Users users = new Users(); users.setRealname("张"); UsersQuery usersQuery = new UsersQuery(); usersQuery.setPage(page); usersQuery.setUsers(users); List list = usersMapper.selectByRealnameAndPage(usersQuery); for (Users u : list) { System.out.println(u); } sqlSession.close();}
4.1.5 Map类型
mapper接口:
List selectUseMap(Map map);
mapper文件:
select id,username,password,realname from users where realname like concat('%',#{name},'%') order by id limit #{begin}, #{size}
测试:
@Testpublic void testMapParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Map map = new HashMap(); map.put("name", "李"); map.put("size", 5); map.put("begin", 0); List list = usersMapper.selectUseMap(map); for (Users u : list) { System.out.println(u); } sqlSession.close();}
4.1.6 多输入参数
MyBatis中允许有多个输入参数,可使用@Param注解。
这种做法类似与Map类型的输入参数,其中@Param注解的value属性值为Map的key,在映射文件中通过ognl可获取对应的value,并且parameterType可以不指定类型。
mapper接口:
Users login(@Param("uname") String username, @Param("pwd") String password);
mapper文件:
select id,username,password,realname from users where username=#{uname} and password=#{pwd}
测试:
@Testpublic void testMultiParam() { SqlSession sqlSession = MybatisUtil.getSession(); UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class); Users users = usersMapper.login("jerry", "456"); System.out.println(users); sqlSession.close();}
4.2 resultType输出映射
4.2.1 表结构
CREATE TABLE `person` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `person_name` varchar(20), `person_age` int(4), `person_address` varchar(50));INSERT INTO `person` VALUES (1, '曹操', 40, '洛阳');INSERT INTO `person` VALUES (2, '刘备', 38, '成都');INSERT INTO `person` VALUES (3, '孙权', 29, '杭州');INSERT INTO `person` VALUES (4, '关羽', 35, '荆州');INSERT INTO `person` VALUES (5, '张飞', 32, '成都');INSERT INTO `person` VALUES (6, '曹仁', 28, '许都');
4.2.2 实体类
package com.newcapec.entity;public class Person { private Integer id; private String personName; private Integer personAge; private String personAddress; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } public Integer getPersonAge() { return personAge; } public void setPersonAge(Integer personAge) { this.personAge = personAge; } public String getPersonAddress() { return personAddress; } public void setPersonAddress(String personAddress) { this.personAddress = personAddress; } @Override public String toString() { return "Person{" + "id=" + id + ", personName='" + personName + '\'' + ", personAge=" + personAge + ", personAddress='" + personAddress + '\'' + '}'; }}
4.2.3 简单类型
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
mapper接口:
package com.newcapec.mapper;public interface PersonMapper { // 查询Person的总数量 Integer selectCount();}
mapper文件:
select count(1) from person
测试:
public class ResultTypeTest { @Test public void testSimpleResult() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); int total = personMapper.selectCount(); System.out.println("总记录数:" + total); sqlSession.close(); }}
4.2.4 实体类对象和列表
不管是输出的实体类是单个对象还是一个列表(list中包括实体类对象),在mapper.xml中resultType指定的类型是一样的
在原始Dao的方式中,通过selectOne和selectList方法来区分返回值为单个对象或集合列表,而在mapper代理中,则通过接口中定义的方法返回值来区分。
mapper接口:
Person selectById(Integer id);List selectAll();
mapper文件:
select id,person_name personName,person_age personAge,person_address personAddress from person where id=#{id} select id,person_name personName,person_age personAge,person_address personAddress from person
测试:
@Testpublic void testResultType1() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); Person person = personMapper.selectById(1); System.out.println(person); sqlSession.close();}@Testpublic void testResultType2() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); List list = personMapper.selectAll(); for (Person person : list) { System.out.println(person); } sqlSession.close();}
4.2.5 resultMap
-
resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和SQL查询的列名一致方可映射成功,当然如果开启下划线转驼峰 或 Sql设置列别名,也可以自动映射。
-
如果SQL查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还会将查询结果映射到实体类对象中。
-
resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和list实现一对一查询和一对多查询。
mapper接口:
List select();
mapper文件:
使用resultMap作为statement的输出映射类型。
select id,person_name,person_age,person_address from person
-
resultType: 自动映射
-
resultMap: 手动映射
-
id: 唯一标识,名称;
-
type: 手动映射的java类型
-
子标签
配置数据库表中的主键和实体类中属性的对应关系
-
子标签
配置数据库表中的普通字段和实体类中属性的对应关系
-
property:实体类中的成员变量名
-
column:结果集中的字段名称
-
javaType:实体类成员变量的类型,由mybaits自动识别,可不配置
-
jdbcType:表字段的类型,由mybaits自动识别,可不配置
-
typeHandler:自定义类型处理器,用的相对比较少
-
-
测试:
@Testpublic void testResultMap() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); List list = personMapper.select(); for (Person person : list) { System.out.println(person); } sqlSession.close();}
4.3 动态SQL
4.3.1 什么是动态SQL
动态Sql是指MyBatis核心对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装。
比如:
我们要查询姓名中带 M 和 高于 1000的员工信息;
可能有时候我们需要不带条件查询;
可能有时候我们需要模糊查询;
可能有时候需要根据多条件查询;
动态SQL可以帮助我们解决这些问题;
通过mybatis提供的各种标签方法实现动态拼接sql。
4.3.2 if标签
判断标签,当参数符合判断条件拼接SQL语句。
实体类:
package com.newcapec.entity;import java.util.Date;public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; private String gender; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + ", gender='" + gender + '\'' + '}'; }}
mapper接口:
public interface EmpMapper { List selectUseIf(Emp emp);}
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
测试:
/* * 动态sql测试 */public class DynamicSqlTest { @Test public void testIf() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setSal(1300.0); emp.setDeptno(20); List list = empMapper.selectUseIf(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close(); }}
4.3.3 where标签
where标签,替代where关键字。
1、当where标签内所有的条件都不成立,不会拼接where关键字,只要有一个条件成立就会在SQL语句中拼接where关键字。
2、where标签会自动剔除条件头部的and或者or关键字。
mapper接口:
List selectUseWhere(Emp emp);
mapper文件:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
测试:
@Testpublic void testWhere() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("S"); emp.setSal(1300.0); emp.setDeptno(20); List list = empMapper.selectUseWhere(emp); for (Emp e : list) { System.out.println(e); } sqlSession.close();}
4.3.4 set标签
set标签,替代set关键字。
1、当set标签内所有的条件都不成立,不会拼接set关键字,只要有一个条件成立就会在SQL语句中拼接set关键字。
2、注意:如果set包含的内容为空SQL语句会出错。
3、set标签会自动剔除条件末尾的任何不相关的逗号。
mapper接口:
void updateUseSet(Emp emp);
mapper文件:
update emp ename=#{ename}, job=#{job}, mgr=#{mgr}, hiredate=#{hiredate}, sal=#{sal}, comm=#{comm}, deptno=#{deptno}, where empno=#{empno}
测试:
@Testpublic void testSet() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEmpno(7938); emp.setEname("JACK"); emp.setJob("MANAGER"); emp.setMgr(7844); emp.setSal(5600.0); emp.setComm(1200.0); emp.setHiredate(new Date()); emp.setDeptno(30); empMapper.updateUseSet(emp); sqlSession.commit(); sqlSession.close();}
4.3.5 trim标签
trim标签属性解析:
-
prefix:前缀,包含内容前加上某些字符。
-
suffix:后缀,包含内容后加上某些字符。
-
prefixOverrides:剔除包含内容前的某些字符。
-
suffixOverrides:剔除包含内容后的某些字符。
mapper接口:
void insertUseTrim(Emp emp);
mapper文件:
insert into emp ename, job, mgr, hiredate, sal, comm, deptno, #{ename}, #{job}, #{mgr}, #{hiredate}, #{sal}, #{comm}, #{deptno},
测试:
@Testpublic void testTrim() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = new Emp(); emp.setEname("CHRIS"); emp.setJob("CLERK"); emp.setMgr(1); emp.setSal(3400.0); emp.setComm(800.0); emp.setHiredate(new Date()); emp.setDeptno(10); empMapper.insertUseTrim(emp); sqlSession.commit(); sqlSession.close();}
代替where标签:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno}
代替set标签:
update emp ename=#{ename}, job=#{job}, mgr=#{mgr}, hiredate=#{hiredate}, sal=#{sal}, comm=#{comm}, deptno=#{deptno}, where empno=#{empno}
4.3.6 foreach标签
向SQL传递数组或list,MyBatis使用foreach解析。
属性解析:
-
collection: 遍历的数组或集合对象名称。
-
SQL只接收一个数组参数,这时SQL解析参数的名称MyBatis固定为array。
-
SQL只接收一个List参数,这时SQL解析参数的名称MyBatis固定为list。
-
如果是通过一个实体类或自定义类型的属性传递到SQL的数组或List集合,则参数的名称为实体类或自定义类型中的属性名。
-
-
index: 为数组的下标。
-
item: 每个遍历生成对象中。
-
open: 开始遍历时拼接的串。
-
close: 结束遍历时拼接的串。
-
separator: 遍历的两个对象中需要拼接的串。
mapper接口:
void deleteUseForeach(Integer[] ids);void insertUseForeach(List empList);
mapper文件:
delete from emp where empno in #{id} insert into emp(ename,job,mgr,hiredate,sal,comm,deptno) values (#{emp.ename},#{emp.job},#{emp.mgr},#{emp.hiredate},#{emp.sal},#{emp.comm},#{emp.deptno})
测试:
@Testpublic void testForeach() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); empMapper.deleteUseForeach(new Integer[]{1, 2, 3, 4}); sqlSession.commit(); sqlSession.close();}@Testpublic void testForeach2() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List empList = new ArrayList(); for (int i = 1; i <= 3; i++) { Emp emp = new Emp(); emp.setEname("TOM" + i); emp.setJob("CLERK" + i); emp.setMgr(1); emp.setSal(4567.0); emp.setComm(123.0); emp.setHiredate(new Date()); emp.setDeptno(10); empList.add(emp); } empMapper.insertUseForeach(empList); //sqlSession.commit(); sqlSession.close();}
4.3.7 choose标签
choose标签、when标签、otherwise标签的组合,类似于if-else-if判断。
select...
4.3.8 SQL片段
将实现的动态SQL判断代码块抽取出来,组成一个SQL片段,其它的statement中就可以引用SQL片段,方便程序员进行开发。
注意:在SQL片段中不要包括where标签。
empno,ename,job,mgr,hiredate,sal,comm,deptno ename like concat('%',#{ename},'%') and sal=#{sal} and deptno=#{deptno} select from emp
五、Mybatis 关联查询
5.1 数据模型分析
5.1.1 表功能介绍
-
用户表: 记录用户的基本信息。
-
订单表: 记录用户所创建的订单(购买商品的订单)。
-
订单详情表: 记录订单的详细信息,即购买商品的信息。
-
商品表: 记录商品的基本信息。
5.1.2 表之间的业务关系
用户表和订单表:
-
用户表 ---> 订单表: 一个用户可以创建多个订单,一对多关系;
-
订单表 ---> 用户表: 一个订单只由一个用户创建,一对一关系;
订单表和订单详情表:
-
订单表 ---> 订单详情表: 一个订单可以包含多个订单详情,因为一个订单可以购买多个商品,每个商品的购买信息在订单详情表中记录,一对多关系;
-
订单详情表 ---> 订单表: 一个订单详情只能包括在一个订单中,一对一关系;
订单详情表和商品表:
-
订单详情表 ---> 商品表: 一个订单详情只对应一个商品信息,一对一关系;
-
商品表 ---> 订单详情表: 一个商品可以包括在多个订单详情,一对多关系;
订单表和商品表:
-
订单表 商品表: 一个订单中包含多个商品,一个商品可以添加在多个订单中,两者是通过订单详情表建立关系,多对多关系;
注意:
-
如果两张表有主外键关联关系,那么他们的业务关系是一对一/一对多,或者是双向一对一(比如用户表和用户详情表)。
-
如果两张表是双向一对多关系,那么他们是多对多关系,并且必然存在一张关系描述表作为中间表。
5.1.3 表结构
用户表:
CREATE TABLE `users` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `username` varchar(20), `password` varchar(50), `realname` varchar(20));INSERT INTO `users` VALUES (1, 'admin', '123456', '管理员');INSERT INTO `users` VALUES (2, 'tom', '123', '汤姆');INSERT INTO `users` VALUES (3, 'jerry', '456', '杰瑞');INSERT INTO `users` VALUES (4, 'zhangsan', '111', '张三');INSERT INTO `users` VALUES (5, 'lisi', '222', '李四');
订单表:
CREATE TABLE `orders` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `order_number` varchar(30), `total_price` double, `status` varchar(5), `user_id` int(11));INSERT INTO `orders` VALUES (1, '201812290838001', 2535, '已评价', 2);INSERT INTO `orders` VALUES (2, '201812290838002', 4704.6, '已签收', 2);INSERT INTO `orders` VALUES (3, '201812290838003', 3620, '已支付', 2);INSERT INTO `orders` VALUES (4, '201812290840001', 600, '已发货', 3);INSERT INTO `orders` VALUES (5, '201812290840002', 280, '未支付', 3);
订单详情表:
CREATE TABLE `orders_detail` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `amount` int(11), `goods_id` int(11), `orders_id` int(11));INSERT INTO `orders_detail` VALUES (1, 1, 1, 1);INSERT INTO `orders_detail` VALUES (2, 3, 8, 1);INSERT INTO `orders_detail` VALUES (3, 1, 2, 2);INSERT INTO `orders_detail` VALUES (4, 2, 7, 2);INSERT INTO `orders_detail` VALUES (5, 1, 3, 3);INSERT INTO `orders_detail` VALUES (6, 6, 6, 3);INSERT INTO `orders_detail` VALUES (7, 2, 4, 4);INSERT INTO `orders_detail` VALUES (8, 1, 5, 5);
商品表:
CREATE TABLE `goods` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `goods_name` varchar(50), `description` varchar(500), `price` double);INSERT INTO `goods` VALUES (1, '手机', '手机', 2499);INSERT INTO `goods` VALUES (2, '笔记本电脑', '笔记本电脑', 4699);INSERT INTO `goods` VALUES (3, 'IPAD', 'IPAD', 3599);INSERT INTO `goods` VALUES (4, '运动鞋', '运动鞋', 300);INSERT INTO `goods` VALUES (5, '外套', '外套', 280);INSERT INTO `goods` VALUES (6, '可乐', '可乐', 3.5);INSERT INTO `goods` VALUES (7, '辣条', '辣条', 2.8);INSERT INTO `goods` VALUES (8, '水杯', '水杯', 12);
5.2 一对一查询
5.2.1 需求
查询订单信息。关联如下:
1、关联查询其相关用户信息。
5.2.2 通过resultType方式实现
实体类:
实体类Orders类不能映射全部字段,需要新创建的实体类,创建一个包括查询字段较多的实体类。OrdersQuery中包含了Orders以及Users需要查询的属性。
package com.newcapec.vo;/** * OrdersQuery值对象,不是entity/po,因为它和数据库中表的字段不是对应关系 */public class OrdersQuery { //订单属性 private Integer id; private String orderNumber; private Double totalPrice; private String status; private Integer userId; //用户属性 private String username; private String password; private String realname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOrderNumber() { return orderNumber; } public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; } public Double getTotalPrice() { return totalPrice; } public void setTotalPrice(Double totalPrice) { this.totalPrice = totalPrice; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } @Override public String toString() { return "OrdersQuery{" + "id=" + id + ", orderNumber='" + orderNumber + '\'' + ", totalPrice=" + totalPrice + ", status='" + status + '\'' + ", userId=" + userId + ", username='" + username + '\'' + ", password='" + password + '\'' + ", realname='" + realname + '\'' + '}'; }}
mapper接口:
package com.newcapec.mapper;import com.newcapec.vo.OrdersQuery;import java.util.List;public interface OrdersMapper { List selectUseResultType();}
mapper文件:
select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname from orders a, users b where a.user_id=b.id
测试:
public class QueryTest { @Test public void testOneToOneResultType() { SqlSession sqlSession = MybatisUtil.getSession(); OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List list = ordersMapper.selectUseResultType(); for (OrdersQuery ordersQuery : list) { System.out.println(ordersQuery); } sqlSession.close(); }}
5.2.3 通过resultMap方式实现
- 5.2.3.1 实体类
用户类:
package com.newcapec.entity;public class Users { private Integer id; private String username; private String password; private String realname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } @Override public String toString() { return "Users{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", realname='" + realname + '\'' + '}'; }}
订单类:
在Orders类中加入Users属性,Users属性用于存储关联查询的用户信息。
因为订单关联查询用户是一对一关系,所以这里使用单个Users对象存储关联查询的用户信息。
package com.newcapec.entity;public class Orders { private Integer id; private String orderNumber; private Double totalPrice; private String status; private Integer userId; /** * 一对一关系属性 */ private Users users; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOrderNumber() { return orderNumber; } public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; } public Double getTotalPrice() { return totalPrice; } public void setTotalPrice(Double totalPrice) { this.totalPrice = totalPrice; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Users getUsers() { return users; } public void setUsers(Users users) { this.users = users; } @Override public String toString() { return "Orders{" + "id=" + id + ", orderNumber='" + orderNumber + '\'' + ", totalPrice=" + totalPrice + ", status='" + status + '\'' + ", userId=" + userId + ", users=" + users + '}'; }}
- 5.2.3.2 mapper接口
List selectUseResultMap();
- 5.2.3.3 mapper文件
association标签: 一对一关系映射描述。
property: 关系属性名称
javaType: 关系属性类型
select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname from orders a, users b where a.user_id=b.id
- 5.2.3.4 测试
@Testpublic void testOneToOneResultMap() { SqlSession sqlSession = MybatisUtil.getSession(); OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List list = ordersMapper.selectUseResultMap(); for (Orders orders : list) { System.out.println(orders); } sqlSession.close();}
5.2.4 resultType和resultMap实现一对一查询小结
-
resultType:使用resultType实现较为简单,如果实体类中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果查询结果没有特殊要求,建议使用resultType。
-
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射到实体类的属性中。
-
resultMap可以实现延迟加载,resultType无法实现延迟加载。
5.3 一对多查询
5.3.1 需求
查询订单信息。关联如下:
1、关联查询其相关用户信息
2、关联查询其相关订单详情信息。
5.3.2 实体类
订单详情类:
public class OrdersDetail { private Integer id; private Integer amount; private Integer ordersId; private Integer goodsId; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getAmount() { return amount; } public void setAmount(Integer amount) { this.amount = amount; } public Integer getOrdersId() { return ordersId; } public void setOrdersId(Integer ordersId) { this.ordersId = ordersId; } public Integer getGoodsId() { return goodsId; } public void setGoodsId(Integer goodsId) { this.goodsId = goodsId; } @Override public String toString() { return "OrdersDetail{" + "id=" + id + ", amount=" + amount + ", ordersId=" + ordersId + ", goodsId=" + goodsId + '}'; }}
订单类:
在Order类中加入List detailList属性,details属性用于存储关联查询的订单详情。
因为订单关联查询订单详情是一对多关系,所以这里使用集合对象存储关联查询的订单详情信息。
public class Orders { private Integer id; private String orderNumber; private Double totalPrice; private String status; private Integer userId; /** * 一对一关系属性 */ private Users users; /** * 一对多关系属性 */ private List detailList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOrderNumber() { return orderNumber; } public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; } public Double getTotalPrice() { return totalPrice; } public void setTotalPrice(Double totalPrice) { this.totalPrice = totalPrice; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Users getUsers() { return users; } public void setUsers(Users users) { this.users = users; } public List getDetailList() { return detailList; } public void setDetailList(List detailList) { this.detailList = detailList; } @Override public String toString() { return "Orders{" + "id=" + id + ", orderNumber='" + orderNumber + '\'' + ", totalPrice=" + totalPrice + ", status='" + status + '\'' + ", userId=" + userId + ", users=" + users + ", detailList=" + detailList + '}'; }}
5.3.3 mapper接口
List selectOrdersAndDetail();
5.3.4 mapper文件
collection标签: 一对多关系映射描述。
property: 关系属性名称
ofType: 关系属性是一个List集合,集合中存放的元素类型
select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname, c.id detail_id,c.amount,c.goods_id from orders a join users b on a.user_id=b.id join orders_detail c on a.id=c.orders_id
5.3.5 测试
@Testpublic void testOneToMany() { SqlSession sqlSession = MybatisUtil.getSession(); OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List list = ordersMapper.selectOrdersAndDetail(); for (Orders orders : list) { System.out.println(orders); } sqlSession.close();}
5.4 多对多查询
5.4.1 订单与商品
- 5.4.1.1 需求
查询订单信息。关联如下:
1、关联查询其相关用户信息
2、关联查询其相关订单详情信息
3、关联查询订单详情中的商品信息
- 5.4.1.2 实体类
将OrderDetail类中Integer类型的goods_id属性修改为Goods类型属性,goods属性用于存储关联查询的商品信息。
订单与订单详情是一对多关系,订单详情与商品是一对一关系,反之商品与订单详情是一对多关系,订单详情与订单是一对一关系,所以订单与商品之前为多对多关系。
商品类:
public class Goods { private Integer id; private String goodsName; private String description; private Double price; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } @Override public String toString() { return "Goods{" + "id=" + id + ", goodsName='" + goodsName + '\'' + ", description='" + description + '\'' + ", price=" + price + '}'; }}
订单详情类:
public class OrdersDetail { private Integer id; private Integer amount; private Integer ordersId; private Integer goodsId; /** * 一对一关系 */ private Goods goods; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getAmount() { return amount; } public void setAmount(Integer amount) { this.amount = amount; } public Integer getOrdersId() { return ordersId; } public void setOrdersId(Integer ordersId) { this.ordersId = ordersId; } public Integer getGoodsId() { return goodsId; } public void setGoodsId(Integer goodsId) { this.goodsId = goodsId; } public Goods getGoods() { return goods; } public void setGoods(Goods goods) { this.goods = goods; } @Override public String toString() { return "OrdersDetail{" + "id=" + id + ", amount=" + amount + ", ordersId=" + ordersId + ", goodsId=" + goodsId + ", goods=" + goods + '}'; }}
- 5.4.1.3 mapper接口
List selectOrdersAndGoods();
- 5.4.1.4 mapper文件
select a.id,a.order_number,a.total_price,a.status,a.user_id,b.username,b.password,b.realname, c.id detail_id,c.amount,c.goods_id,d.goods_name,d.description,d.price from orders a join users b on a.user_id=b.id join orders_detail c on a.id=c.orders_id join goods d on c.goods_id=d.id
- 5.4.1.5 测试
@Testpublic void testManyToMany1() { SqlSession sqlSession = MybatisUtil.getSession(); OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List list = ordersMapper.selectOrdersAndGoods(); for (Orders orders : list) { System.out.println(orders); } sqlSession.close();}
5.4.2 学生与课程之间的多对多关系
- 5.4.2.1 需求
查询学生信息,并关联查询学生相应的课程信息。
- 5.4.2.2 表结构
CREATE TABLE `course` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `cname` varchar(20));INSERT INTO `course` VALUES (1, '大学语文');INSERT INTO `course` VALUES (2, '大学英语');INSERT INTO `course` VALUES (3, '高等数学');INSERT INTO `course` VALUES (4, 'JAVA语言');INSERT INTO `course` VALUES (5, '网络维护');INSERT INTO `course` VALUES (6, '通信原理');CREATE TABLE `student` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `name` varchar(20), `gender` varchar(20), `major` varchar(20));INSERT INTO `student` VALUES (1, '小明', '男', '软件工程');INSERT INTO `student` VALUES (2, '小红', '女', '网络工程');INSERT INTO `student` VALUES (3, '小丽', '女', '物联网');CREATE TABLE `student_course` ( `id` int(11) PRIMARY KEY AUTO_INCREMENT, `student_id` int(11), `course_id` int(11));INSERT INTO `student_course` VALUES (1, 1, 1);INSERT INTO `student_course` VALUES (2, 1, 3);INSERT INTO `student_course` VALUES (3, 1, 4);INSERT INTO `student_course` VALUES (4, 2, 1);INSERT INTO `student_course` VALUES (5, 2, 2);INSERT INTO `student_course` VALUES (6, 2, 5);INSERT INTO `student_course` VALUES (7, 3, 2);INSERT INTO `student_course` VALUES (8, 3, 3);INSERT INTO `student_course` VALUES (9, 3, 6);
- 5.4.2.3 实体类
课程类:
public class Course { private Integer id; private String cname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } @Override public String toString() { return "Course{" + "id=" + id + ", cname='" + cname + '\'' + '}'; }}
学生类:
public class Student { private Integer id; private String name; private String gender; private String major; /** * 一对多 */ private List courseList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getMajor() { return major; } public void setMajor(String major) { this.major = major; } public List getCourseList() { return courseList; } public void setCourseList(List courseList) { this.courseList = courseList; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", major='" + major + '\'' + ", courseList=" + courseList + '}'; }}
- 5.4.2.4 mapper接口
public interface StudentMapper { List select();}
- 5.4.2.5 mapper文件
select a.id,a.name,a.gender,a.major,b.course_id,c.cname from student a join student_course b on a.id=b.student_id join course c ON b.course_id=c.id
- 5.4.2.6 测试
@Testpublic void testManyToMany2() { SqlSession sqlSession = MybatisUtil.getSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); List list = studentMapper.select(); for (Student student : list) { System.out.println(student); } sqlSession.close();}
5.5 关联查询总结
5.5.1 resultType
作用:将查询结果按照SQL列名与实体类属性名一致性映射到实体类对象中。
场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到实体类中,在前端页面遍历list(list中是实体类)即可。
5.5.2 resultMap
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association
作用:将关联查询信息映射到一个实体类对象中。
场合:为了方便查询关联信息可以使用association将关联信息映射为当前对象的一个属性,比如:查询订单以及关联用户信息。
collection
作用:将关联查询信息映射到一个list集合中。
场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如: 查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果映射到list集合中。
resultMap的继承
resultMap标签可以通过extends属性来继承一个已有的或公共的resultMap,避免重复配置的出现,减少配置量。
例子如下:
六、Mybatis 延迟加载
6.1 什么是延迟加载
需要查询关联信息时,使用MyBatis延迟加载特性可有效的减少数据库压力,首次查询只查询主要信息,关联信息等用户获取时再加载。
懒加载针对级联使用的,懒加载的目的是减少内存的浪费和减轻系统负担。你可以理解为按需加载,当我调用到关联的数据时才与数据库交互否则不交互。
resultMap可以实现高级映射(使用association、collection实现一对一和一对多映射),association、collection具备延迟加载功能。
6.2 打开延迟加载开关
在MyBatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading。
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载 | true, false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 | true, false | true |
6.3 实体类
6.3.1 部门类
public class Dept { private Integer deptno; private String dname; private String loc; /** * 关系属性 */ private List empList; public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } public List getEmpList() { return empList; } public void setEmpList(List empList) { this.empList = empList; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; }}
6.3.2 员工类
public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; /** * 关系属性 */ private Dept dept; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + '}'; }}
6.4 使用association实现延迟加载
查询员工以及相关联的部门信息。
6.4.1 mapper接口
public interface EmpMapper { /* * 查询所有员工的信息,并关联查询部门信息 */ List select();}public interface DeptMapper { /* * 根据部门编号查询部门信息 */ Dept selectById(Integer deptno);}
6.4.2 mapper文件
懒加载的前提是需要分离Sql,不再使用关联查询Sql。
比如我们要查询所有员工的信息,并关联查询部门信息;想要实现部门信息懒加载,那么查询员工信息是一条独立的Sql,根据部门编号查询部门信息也是一条独立的Sql,当查询员工信息的时候,如果需要用到部门信息,那么就调用根据部门编号查询部门信息的Sql。
根据部门编号查询部门信息:
select deptno,dname,loc from dept where deptno=#{deptno}
查询员工信息(单表查询),并通过上边的查询去关联部门信息:
association标签的属性:
select:执行延迟加载时关联数据查询的sql对应的statementId。
执行的关联查询语句在同一mapper文件中:直接填入statementId即可;
执行的关联查询语句在不同的mapper文件中:namespace.statementId;
column:在执行关系表信息查询时,与其关联的字段名称。
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp
6.4.3 测试
public class LazyLoadingTest { @Test public void testOneToOne() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List list = empMapper.select(); for (Emp emp : list) { System.out.println(emp.getEmpno() + "--" + emp.getEname() + "--" + emp.getJob()); System.out.println("----------"); // 需要使用关联信息 Dept dept = emp.getDept(); if (dept != null) { System.out.println(dept.getDeptno() + "--" + dept.getDname() + "--" + dept.getLoc()); } System.out.println("-----------------分割线----------------"); } sqlSession.close(); }}
6.5 使用collection实现延迟加载
6.5.1 mapper接口
public interface DeptMapper { /* * 查询所有部门信息,并关联部门对应的员工信息 */ List select();}public interface EmpMapper { /* * 根据部门编号查询对应的员工信息 */ List selectByDeptno(Integer deptno);}
6.5.2 mapper文件
根据部门编号查询员工信息:
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where deptno=#{deptno}
查询部门信息(单表查询),并通过上边的查询去关联部门中的员工信息:
select deptno,dname,loc from dept
6.5.3 测试
@Testpublic void testOneToMany() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List list = deptMapper.select(); for (Dept dept : list) { System.out.println(dept.getDeptno() + "--" + dept.getDname() + "--" + dept.getLoc()); //查询关联信息 List empList = dept.getEmpList(); for (Emp emp : empList) { System.out.println("员工姓名:" + emp.getEname()); } } sqlSession.close();}
七、Mybatis 查询缓存
缓存:将数据临时存储在存储介质(内存,文件)中,关系型数据库的缓存目的就是为了减轻数据库的压力。
数据库的数据实际是存储在硬盘中,如果我们程序需要用到数据,那么就需要频繁的从磁盘中读取数据,效率低,数据库压力大。我们可以把查询到的数据缓存起来,这样就减少了频繁操作磁盘数据,提高查询效率,减轻服务器压力。
Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能。但是在实际项目开发中,很少使用Mybatis的缓存机制,现在主流的缓存机制是redis。
7.1 什么是查询缓存
-
MyBatis提供查询缓存,用于减轻数据库压力,提高数据库性能;
-
MyBatis提供一级缓存,和二级缓存;
-
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的;
-
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的;
7.2 一级缓存
7.2.1 一级缓存工作原理
-
第一次查询id为1的数据,先去找一级缓存中查找是否有id为1的数据,如果没有,从数据库中查询该数据,并将该数据存储到一级缓存中;
-
第二次查询id为1的数据,也先去找一级缓存中查找是否有id为1的数据,缓存中有,直接从缓存中获取该数据,不再查询数据库;
-
如果SqlSession去执行commit操作(执行插入、更新、删除),将清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的数据,避免脏读;
7.2.2 实体类
public class Person { private Integer id; private String personName; private Integer personAge; private String personAddress; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } public Integer getPersonAge() { return personAge; } public void setPersonAge(Integer personAge) { this.personAge = personAge; } public String getPersonAddress() { return personAddress; } public void setPersonAddress(String personAddress) { this.personAddress = personAddress; } @Override public String toString() { return "Person{" + "id=" + id + ", personName='" + personName + '\'' + ", personAge=" + personAge + ", personAddress='" + personAddress + '\'' + '}'; }}
7.2.3 mapper接口
public interface PersonMapper { Person selectById(Integer id); List select();}
7.2.4 mapper文件
select id,person_name,person_age,person_address from person where id=#{id} select id,person_name,person_age,person_address from person
7.2.5 测试
public class CacheTest { @Test public void testSqlSessionCache() { SqlSession sqlSession = MybatisUtil.getSession(); PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class); System.out.println("----------第一次使用id为1的person数据-----------"); Person p1 = personMapper.selectById(1); System.out.println(p1); /** * 一级缓存自带缓存,不可不用的,缓存介质为内存 * commit()提交方法可以请求缓存 */ //sqlSession.commit(); System.out.println("----------第二次使用id为1的person数据-----------"); Person p2 = personMapper.selectById(1); System.out.println(p2); sqlSession.close(); }}
7.3 二级缓存
7.3.1 二级缓存工作原理
-
SqlSession1去查询id为1的数据,查询到后会将该数据存储到二级缓存中;
-
SqlSession2去查询id为1的数据,去缓存中找是否存在数据,如果存在直接从缓存中取出数据;
-
如果SqlSession3去执行相同mapper下sql,执行commit提交,清空该mapper下的二级缓存区域的数据;
-
二级缓存与一级缓存区别,二级缓存的范围更大,多个SqlSession可以共享一个Mapper的二级缓存区域;
-
每个mapper有一个二级缓存区域,按namespace分;
-
如果两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中;
7.3.2 开启二级缓存
在mybatis核心配置文件中配置:cacheEnabled
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置 | true \ false | true |
在映射文件中开启二缓存,mapper.xml下的SQL执行完成会存储到它的缓存区域HashMap。
...
7.3.3 实体类
二级缓存中存储数据的实体类必须实现可序列化接口java.io.Serializable。
public class Person implements Serializable { }
7.3.4 二级缓存测试
@Testpublic void testMapperCache(){ /** * 二级缓存,可插拔式缓存,缓存介质:内存+磁盘 */ SqlSession sqlSession1 = MybatisUtil.getSession(); PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class); System.out.println("-------------第一次查询--------------"); Person p1 = personMapper1.selectById(2); System.out.println(p1); sqlSession1.close(); System.out.println("----------------sqlSession1关闭,建立sqlSession2连接-------------------"); SqlSession sqlSession2 = MybatisUtil.getSession(); PersonMapper personMapper2 = sqlSession2.getMapper(PersonMapper.class); System.out.println("-------------第二次查询--------------"); Person p2 = personMapper2.selectById(2); System.out.println(p2); sqlSession2.close();}
7.3.5 useCache配置
在statement中设置useCache="false"可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询。默认情况是true,即该sql使用二级缓存。
select id,person_name,person_age,person_address from person where id=#{id} select id,person_name,person_age,person_address from person
测试:
@Testpublic void testMapperCache2() { SqlSession sqlSession1 = MybatisUtil.getSession(); PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class); System.out.println("-------------第一次查询--------------"); List list1 = personMapper1.select(); System.out.println(list1); sqlSession1.close(); System.out.println("----------------sqlSession1关闭,建立sqlSession2连接-------------------"); SqlSession sqlSession2 = MybatisUtil.getSession(); PersonMapper personMapper2 = sqlSession2.getMapper(PersonMapper.class); System.out.println("-------------第二次查询--------------"); List list2 = personMapper2.select(); System.out.println(list2); sqlSession2.close();}
八、Mybatis PageHelper分页插件
8.1 导入分页插件jar包
方式一:导入jar包
方式二:配置maven依赖
com.github.pagehelper pagehelper 5.1.10
8.2 配置分页插件
8.2.1 在MyBatis全局配置文件中配置拦截器插件
8.2.2 配置插件属性
属性名称 | 默认值 | 描述 |
---|---|---|
helperDialect | 分页插件会自动检测当前的数据库链接,自动选择合适的分页方式你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby | |
offsetAsPageNum | false | 该参数对使用 RowBounds作为分页参数时有效当该参数设置为 true时,会将 RowBounds中的 offset参数当成 pageNum使用,可以用页码和页面大小两个参数进行分页 |
rowBoundsWithCount | false | 该参数对使用 RowBounds作为分页参数时有效当该参数设置为true时,使用 RowBounds分页会进行 count查询 |
pageSizeZero | false | 当该参数设置为 true时,如果 pageSize=0 或者 RowBounds.limit=0 就会查询出全部的结果,相当于没有执行分页查询,但是返回结果仍然是 Page类型 |
reasonable | false | 分页合理化参数启用合理化时,如果pageNumpages会查询最后一页禁用合理化时,如果pageNumpages会返回空数据 |
params | pageNum=pageNum; pageSize=pageSize; count=countSql; reasonable=reasonable; pageSizeZero=pageSizeZero | 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 |
supportMethodsArguments | false | 支持通过 Mapper接口参数来传递分页参数分页插件会从查询方法的参数值中,自动根据上面 params配置的字段中取值,查找到合适的值时就会自动分页 |
autoRuntimeDialect | false | 设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 |
closeConn | true | 当使用运行时动态数据源或没有设置 helperDialect属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定 |
8.3 在程序中的使用
8.3.1 实体类
public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + '}'; }}
8.3.2 mapper接口
public interface EmpMapper { List selectByPage();}
8.3.3 mapper文件
select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp order by empno
8.3.4 测试
public class PageHelperTest { @Test public void testPageHelper() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //开启分页拦截器,设置分页的基本属性(当前页面数,每页条数) PageHelper.startPage(1, 5); List empList = empMapper.selectByPage(); for (Emp emp : empList) { System.out.println(emp); } //分页信息对象 PageInfo pageInfo = new PageInfo(empList); System.out.println("当前页数:" + pageInfo.getPageNum()); System.out.println("每页条数:" + pageInfo.getPageSize()); System.out.println("总记录数:" + pageInfo.getTotal()); System.out.println("总页数:" + pageInfo.getPages()); System.out.println("上一页:" + pageInfo.getPrePage()); System.out.println("下一页:" + pageInfo.getNextPage()); System.out.println("是否有上一页:" + pageInfo.isHasPreviousPage()); System.out.println("是否有下一页:" + pageInfo.isHasNextPage()); System.out.println("是否为首页:" + pageInfo.isIsFirstPage()); System.out.println("是否为末页:" + pageInfo.isIsLastPage()); System.out.println("存放页码的数据:" + Arrays.toString(pageInfo.getNavigatepageNums())); System.out.println("获取当前页数据:" + pageInfo.getList()); sqlSession.close(); }}
九、Mybatis Generator代码生成
虽然MyBatis是一个简单易学的框架,但是配置XML文件也是一件相当繁琐的一个过程,而且会出现很多不容易定位的错误。当在工作中需要生成大量对象的时候,有太多的重复劳动,简直是生无可恋。
为此官方开发了MyBatis Generator。它只需要很少量的简单配置,就可以完成大量的表到Java对象的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。
9.1 生成文件介绍
MyBatis Generator生成的文件包含三类:
1、Model实体文件,一个数据库表对应生成一个 Model 实体;
2、Mapper接口文件,数据数操作方法都在此接口中定义;
3、Mapper XML配置文件;
9.2 配置依赖
org.mybatis mybatis 3.4.6 mysql mysql-connector-java 5.1.49 log4j log4j 1.2.17 junit junit 4.12 test org.mybatis.generator mybatis-generator-core 1.3.7
9.3 引入相关配置
我们只需引入log4j.properties即可,无需引入mybatis-config.xml。
9.4 生成配置文件
generator.xml:
Mybatis Generator最完整配置详解:
https://blog.csdn.net/qq_33326449/article/details/105930655
9.5 生成文件代码
public class Generator { public static void main(String[] args) throws Exception { //是否覆盖已有文件 boolean overwirte = true; DefaultShellCallback callback = new DefaultShellCallback(overwirte); List warnings = new ArrayList(); //创建配置解析类 ConfigurationParser configurationParser = new ConfigurationParser(warnings); InputStream in = Generator.class.getClassLoader().getResourceAsStream("generator.xml"); Configuration configuration = configurationParser.parseConfiguration(in); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(configuration, callback, warnings); myBatisGenerator.generate(null); System.out.println("代码生成成功..."); }}
9.6 第三方插件
9.6.1 Free Mybatis Tool
安装插件
File -> Settings -> Plugins -> 搜索框输入:Free Mybatis Tool,点击Install安装。
注意:安装完成之后,最好重启IDEA。
逆向生成mapper、类
通过这个插件不用使用官方的mybatis逆向生成包,写配置文件等等,仅需连接对应数据库就可以实现逆向生成对应的类、mapper文件等。
第一步:连接数据库
第二步:配置Driver(首次使用)
点击Driver:Mysql -> Go to Driver,配置MySQL驱动;
第三步:找到需要逆向生成的表右键选择Mybatis-Generator
第四步:配置
跳转功能
在使用mybatis框架的时候,你还在一个类一个类的点开寻找对应mapper或者dao程序的位置吗?那样就会显得特别麻烦且浪费时间。而这个Free Mybatis Tool插件提供了跳转的功能。通过点击箭头就可以跳转到相应的地方。
9.6.2 Easy Code
EasyCode是idea的一个插件,可以采用图形化的方式对数据的表生成entity,controller,service,dao,mapper……无需任何编码,简单而强大。
安装插件
File -> Settings -> Plugins -> 搜索框输入:EasyCode,点击Install安装。
注意:安装完成之后,最好重启IDEA。
逆向生成mapper、类
找到需要逆向生成的表右键选择EasyCode。
1、Generate Code,代码生成;
2、Config Table,配置表信息;
十、Mybatis 注解开发
10.1 什么是注解开发
Mybatis最初配置信息是基于XML,映射语句(SQL)也是定义在 XML 中的。而到了 MyBatis 3提供了新的基于注解的配置。使用注解开发方式,可以减少编写 Mapper 映射文件。
10.2 常用注解说明
注解 | 描述 |
---|---|
@Insert | 配置新增 |
@Update | 配置更新 |
@Delete | 配置删除 |
@Select | 配置查询 |
@Options | 配置主键返回,关闭二级缓存等功能 |
@Result | 结果集封装 |
@Results | 与@Result 一起使用,封装多个结果集 |
@ResultMap | 引用@Results 定义的封装 |
@One | 一对一结果集封装 |
@Many | 一对多结果集封装 |
@SelectProvider | 动态 SQL 映射 |
@CacheNamespace | 二级缓存 |
@Param | 输入多参数 |
@Mapper | 把mapper这个DAO交給Spring管理,整合用到 |
10.3 实体类
10.3.1 部门类
public class Dept { private Integer deptno; private String dname; private String loc; public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } @Override public String toString() { return "Dept{" + "deptno=" + deptno + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; }}
10.3.2 员工类
public class Emp { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; private Dept dept; public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Integer getDeptno() { return deptno; } public void setDeptno(Integer deptno) { this.deptno = deptno; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", job='" + job + '\'' + ", mgr=" + mgr + ", hiredate=" + hiredate + ", sal=" + sal + ", comm=" + comm + ", deptno=" + deptno + ", dept=" + dept + '}'; }}
10.4 单表增删改差
10.4.1 mapper接口
public interface DeptMapper { @Select("select deptno,dname,loc from dept") List select(); @Select("select deptno,dname,loc from dept where deptno = #{deptno}") Dept selectById(Integer deptno); @Insert("insert into dept(dname,loc) values (#{dname}, #{loc})") @Options(useGeneratedKeys = true, keyProperty = "deptno", keyColumn = "deptno") void insert(Dept dept); @Update("update dept set dname = #{dname},loc=#{loc} where deptno = #{deptno}") void update(Dept dept); @Delete("delete from dept where deptno=#{deptno}") void delete(Integer deptno);}
10.4.2 测试
public class AnnotationTest { @Test public void testSelect() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List list = deptMapper.select(); for (Dept dept : list) { System.out.println(dept); } sqlSession.close(); } @Test public void testSelectById() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = deptMapper.selectById(10); System.out.println(dept); sqlSession.close(); } @Test public void testInsert() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = new Dept(); dept.setDname("aa"); dept.setLoc("aa"); deptMapper.insert(dept); sqlSession.commit(); System.out.println("主键:" + dept.getDeptno()); sqlSession.close(); } @Test public void testUpdate() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); Dept dept = new Dept(); dept.setDeptno(41); dept.setDname("bb"); dept.setLoc("bb"); deptMapper.update(dept); sqlSession.commit(); sqlSession.close(); } @Test public void testDelete() { SqlSession sqlSession = MybatisUtil.getSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); deptMapper.delete(41); sqlSession.commit(); sqlSession.close(); }}
10.5 一对一关系映射
10.5.1 mapper接口
public interface EmpMapper { /* * 手动映射resultMap标签 * @Results + @Result注解替代 * @Results = resultMap标签 * @Result = resultMap标签的子标签id和result */ @Select("select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp") @Results(id = "selectResultMap", value = { @Result(id = true, 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"), @Result(column = "deptno", property = "deptno"), @Result(column = "deptno", property = "dept", javaType = Dept.class, one = @One(select = "com.newcapec.dao.DeptDao.selectById", fetchType = FetchType.LAZY)) }) List select(); @Select("select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where empno=#{empno}") @ResultMap("selectResultMap") Emp selectById(Integer empno);}
10.5.2 测试
@Testpublic void testSelectEmp() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List empList = empMapper.select(); for (Emp emp : empList) { System.out.println(emp); } sqlSession.close();}@Testpublic void testSelectEmpById() { SqlSession sqlSession = MybatisUtil.getSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Emp emp = empMapper.selectById(7369); System.out.println(emp); sqlSession.close();}