01. 介绍
1.1、三层架构1.2、MyBatis 02. jdbc问题总结03. 快速使用MyBatis
3.1、项目结构3.2、基本使用步骤
3.2.1 概述3.2.2 导入jar包
MyBatisMySQLServlet、JspLombok 3.2.3 创建表对应的Pojo类3.2.4 添加配置文件
SqlMapConfig.xmlmapper.xmldb.properties 3.2.5 测试开始(增删改查)
测试步骤查增删改 3.3、SQL语句中的参数
3.3.1、一个简单参数3.3.2、多个参数
1、使用@Param(常用)2、使用对象(主要使用)3、使用位置(了解)4、使用Map(了解) 3.4、使用小结
3.4.1、mybatis配置/架构说明
1、MyBatis架构2、说明
配置文件说明架构说明 3.4.2、使用到的各个类、接口说明3.4.3、parameterType和resultType3.4.4、typeAliases(类型别名)3.4.5、占位符#{} 和 ${}(面试会问)
#{}${}区别 3.4.6、selectOne和selectList 04. Mapper动态代理方式开发
4.1、原始Dao开发的流程4.2、原始Dao开发中存在的问题4.3、Mapper动态代理开发规范4.4、动态代理开发实战
1、实体类2、mapper配置文件3、Dao接口4、测试类 05. 封装MyBatis输出结果
5.1、resultType5.2、resultMap5.3、resultType和resultMap的区别5.4、pojo类中属性与表列名不一致处理方式 06. QueryVo07. 动态SQL
7.1、简介7.2、基础使用7.3、使用include提取SQL语句中相同的部分7.4、使用foreach标签 08. MySQL自增主键返回
需求实现 09. 控制台打印日志
1、使用mybatis自带的2、Log4j的使用 10、SqlMapConfig文件中一次性指定多个mapper11. 关联查询
11.1 一对一查询
11.1.1 实现方式一:使用ResultType
1、根据查询结果创建一个pojo类,用来存储返回的数据2、创建一个mapper3、编写Dao接口4、开始测试 11.1.2 实现方式二:使用ResultMap
1、在Order类中加入User属性2、使用`resultMap`标签中的`association`进行关联3、在Dao接口中添加相应的方法4、开始测试 11.2 一对多查询
1、在User中添加`List orders`属性2、使用`resultMap`标签中的`collection`进行关联
注意:这里有一个易错的经典错误 3、在Dao接口中添加相应的方法4、开始测试 11.3 嵌套查询
11.3.1 表结构11.3.2 需求11.3.3 实现
1、创建pojo类2、创建Dao接口3、创建`mapper_employees.xml`资源文件4、在`SqlMapConfig.xml`文件中注册`mapper`5、开始测试 12. pagehelper分页
1、导包2、在`SqlMapConfig.xml`文件中配置3、开始使用pagehelper4、PageInfo类里面的属性
01. 介绍 1.1、三层架构三层架构包含的三层:
界面层(User Interface layer):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和 用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。业务逻辑层(Business Logic Layer):接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。数据访问层(Data access layer):与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交 给业务层,同时将业务层处理的数据保存到数据库。
| 三层架构 | 对应的包 | 对应的类 | 对应的高级框架 |
|---|---|---|---|
| 界面层 | controller(控制器) | xxxServlet类 | SpringMVC |
| 业务逻辑层 | service(服务) | xxxService类 | Spring |
| 数据访问层 | dao(Data Access Object数据访问对象) | xxxDao类 | MyBatis |
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、 结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回.
02. jdbc问题总结
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。如果使用数据库连接池可解决此问题.Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变 java代码.使用PreparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护.对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象(持久化类)解析比较方便.
03. 快速使用MyBatis
这里没有使用到任何开发模式,只是描述了快速使用MyBatis的步骤。
3.1、项目结构 3.2、基本使用步骤 3.2.1 概述- 导入jar包:mybatis、mysql、lombok(在pom文件中加入mybatis坐标)创建实体类(每一张表对应一个类,也叫pojo类(Plain Old Java Object:持久化类))创建sql映射文件:mapper_xxx.xml文件(一个表对应一个映射文件)创建mybatis主配置文件:SqlMapConfig.xml文件(一个项目对应一个主配置文件,别忘了在里面指定mapper文件)创建使用mybatis的对象Sqlsession,通过它的方法执行sql语句
MySQLorg.mybatis mybatis 3.4.1
Servlet、Jspmysql mysql-connector-java 8.0.18
导入Servlet、jsp包后就不需要手动导入tomcat里面的依赖包了。
Lombokjavax.servlet javax.servlet-api 3.1.0 provided javax.servlet.jsp jsp-api 2.1 provided
Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
org.projectlombok lombok 1.18.12 provided
需要双击该jar包安装好后才能使用(路径选择为D:Developer_Toolseclipseeclipseeclipse.exe)
| 注解 | 说明 |
|---|---|
| @Data | @Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。 |
| @Getter/@Setter | 如果觉得@Data太过残暴(因为@Data集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor的所有特性)不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法 |
| @Cleanup | 该注解能帮助我们自动调用close()方法,很大的简化了代码。 |
| @ToString | 类使用@ToString注解,Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。 |
| @NoArgsConstructor | 生成无参构造 |
| @AllArgsConstructor | 生成全参构造 |
使用示例
@Getter // get方法
@Setter // set方法
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造
@ToString // tostring方法
public class Employees {
private Integer employeeNumber;
private String lastName;
private String firstName;
private String extension;
private String email;
private String officeCode;
private Integer reportsTo;
private String jobTitle;
}
3.2.3 创建表对应的Pojo类
package com.atSchool.Pojo;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class HouseUser {
private Integer UID; // 这里必须和表中的列名一样
private String uName;
private String uPassWord;
}
3.2.4 添加配置文件
都是在项目的/MavenProject/src/main/resources目录下创建
SqlMapConfig.xml内容在如下网址中有
https://mybatis.org/mybatis-3/zh/getting-started.html
示例:SqlMapConfig.xml
mapper.xml
这里可以不用取名为mapper.xml
sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在 SqlMapConfig.xml中加载.
示例:mapper_book.xml
db.properties
在SqlMapConfig.xml文件中配置的数据库信息是静态的,如果我们将项目打包后,则很难进行修改,所以我们最好是配置一个动态的信息。就和JDBC中的使用配置文件连接数据库是一样的。
设置步骤:
在resource下创建db.properties文件
className=com.mysql.cj.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/house?serverTimezone=UTC&characterEncoding=UTF-8 username=root password=password
在SqlMapConfig.xml中配置
创建SqlSessionFactoryBuilder对象
加载SqlMapConfig.xml配置文件
(因为SqlMapConfig文件中已经制定了mapper文件,所以只需要读取它就可以了)
创建SqlSessionFactory对象
创建SqlSession对象
执行SqlSession对象执行sql语句,获取结果
提交(该步骤可以省略)
(mybatis默认不是自动提交事务的,所以在增删改后需要手动提交。
该步骤也可以省略,前提是获取SqlSession对象时使用的是openSession(true),具体看3.3.2)
释放资源
在/MavenProject/src/test/java文件夹下创建包,再创建文件写代码:(一种规范)
查Tools.java 工具类
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;
public class Tools {
private static final SqlSessionFactory build;
// 这里没有设置为null,是因为常量一旦被赋值就不能修改
static {
// 1. 创建 SqlSessionFactoryBuilder 对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载 SqlMapConfig.xml 配置文件
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
// 3. 创建 SqlSessionFactory 对象
build = sqlSessionFactoryBuilder.build(resourceAsStream);
}
public static SqlSession getSqlSession() {
// 4. 创建 SqlSession 对象
SqlSession sqlSession = null;
if (build != null) {
sqlSession = build.openSession();
}
return sqlSession;
}
}
import java.util.List;
import com.atSchool.Pojo.HouseUser;
public class TestDemo {
public static void main(String[] args) throws IOException {
queryUser();
}
// 查询所有的用户信息
public static void queryUser() {
// 4. 创建`SqlSession`对象
SqlSession openSession = Tools.getSqlSession();
// 5. 执行`SqlSession`对象执行查询,获取结果User。
// 传入的参数是mapper.xml配置文件中sql语句的id
// 也可以是 mapper 文件中 mapper 标签中的namespace+id
List selectList = openSession.selectList("queryUser");
// 6. 打印结果
for (HouseUser houseUser : selectList) {
System.out.println(houseUser);
}
// 7. 释放资源
openSession.close();
}
}
增
import java.util.List;
import com.atSchool.Pojo.HouseUser;
public class TestDemo {
public static void main(String[] args) throws IOException {
addUser();
}
// 添加user
public static void addUser() {
// 4. 创建`SqlSession`对象
SqlSession openSession = Tools.getSqlSession();
int insert = openSession.insert("insertUser", new HouseUser("Maria", "123456"));
System.out.println("受影响的行数为:" + insert);
// 6. 提交
openSession.commit();
// 7. 释放资源
openSession.close();
}
}
删
import java.util.List;
import com.atSchool.Pojo.HouseUser;
public class TestDemo {
public static void main(String[] args) throws IOException {
deleteUser();
}
// 删除用户
public static void deleteUser() {
// 4. 创建`SqlSession`对象
SqlSession openSession = Tools.getSqlSession();
// 5. 执行`SqlSession`对象执行修改,传入的是sql id和参数
int delete = openSession.delete("deleteUser", 23);
System.out.println("受影响的行数为:" + delete);
// 6. 提交
openSession.commit();
// 7. 释放资源
openSession.close();
}
}
改
import java.util.List;
import com.atSchool.Pojo.HouseUser;
public class TestDemo {
public static void main(String[] args) throws IOException {
updateUser();
}
// 修改用户信息
public static void updateUser() {
// 4. 创建`SqlSession`对象
SqlSession openSession = Tools.getSqlSession();
// 5. 执行`SqlSession`对象执行修改
int update = openSession.update("updateUser", new HouseUser(21, "Jane", "12345"));
System.out.println("受影响的行数为:" + update);
// 6. 提交
openSession.commit();
// 7. 释放资源
openSession.close();
}
}
3.3、SQL语句中的参数
3.3.1、一个简单参数
简单参数:Java基本类型和String类型
使用 #{} 或者 @Param。
3.3.2、多个参数 1、使用@Param(常用)思想:当需要使用多个参数时,通过类似取别名的概念进行参数标识。
使用:
接口中
ListselectStudent( @Param(“personName”) String name ) { … }
mapper文件中
思想:使用自定义类(表对应的实体类)中的属性,在resultType中本身就会使用到表对应的实体类,这里使用会更加的直观。
使用:
接口中
ListselectMultiObject(QueryParam param);
完整的使用语法(太繁杂我们一般不用):
语法:#{属性名, javaType=类型名称 ,jdbcType=数据类型}
参数:javaType:Java中的数据类型 ,jdbcType:数据库中的数据类型
简化后的语法:
mybatis会通过反射获取javaType和jdbcType的值,不需要我们提供。
语法:#{属性名}
思想:按照sql语句中参数的位置从左往右依次获取对应的值。
使用:
接口中
ListselectMultiObject(String name,int age);
mapper中
mybatis版本 <= 3.3,使用#{0}、#{1}....
mybatis版本 >= 3.4,使用#{arg0}、#{arg1}....
缺点:
按位置来,如果参数变化,或者位置写错,都会导致运行出错,不直观、方便,一般我们不使用。
4、使用Map(了解)使用:
接口中
ListselectMultiByMap(Mapkstring,object> map);
mapper中
#{key值}
使用代码里面
Mapmap = new HashMap<>; map.put("paramName":"张三"); map.put("paramAge":19");
| 主要配置文件 | 说明 |
|---|---|
| SqlMapConfig.xml | 此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息. |
| mapper.xml | 即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在 SqlMapConfig.xml中加载. |
SqlMapConfig.xml中配置的内容和顺序:
properties(属性)settings(全局配置参数)typeAliases(类型别名)typeHandlers(类型处理器)objectFactory(对象工厂)plugins(插件)environments(环境集合属性对象)environment(环境子属性对象)transactionManager(事务管理)dataSource(数据源)mappers(映射器) 架构说明
| 架构 | 说明 |
|---|---|
| SqlSessionFactory | 通过mybatis环境等配置信息构造SqlSessionFactory(即会话工厂),由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行. |
| Executor | mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器. |
| Mapped Statement | 1. Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等. mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id. 2. Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo(持久化类),Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数. 3. Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo, Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程. |
| 类/接口 | 说明 | 备注 |
|---|---|---|
| SqlSessionFactoryBuilder 类 | 1、SqlSessionFactoryBuilder用于创建SqlSessionFacoty。 2、SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory创建的。所以可以将 SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量 | |
| Resouce类 | mybatis中的一个类,负责读取主配置文件 | |
| SqlSessionFactory 接口 | 用于获取SqlSession对象。SqlSessionFactory定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理 SqlSessionFactory. | 1、openSession():无参数的,获取的是非自动提交事务的SqlSession对象 2、openSession(boolean):根据参数获取SqlSession对象;true:获取自动提交的;false:获取的是非自动提交的 |
| SqlSession 接口 | 1、SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。 2、SqlSession通过SqlSessionFactory创建。 3、SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。 | SqlSession对象不是线程安全的,需要在方法内部使用。在执行sql语句之前,使用openSession()获取SqlSession对象;在执行sql语句之后,需要close()关闭。这样才能保证它是安全的。 |
| 说明 | 备注 | |
|---|---|---|
| parameterType | 指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。值为Java中的数据类型全限定名称或者是mybatis定义的别名。 | 该参数不是强制的,mybatis通过反射机制能够发现接口参数的数据类型,所以一般我们不写 |
| resultType | 指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中 | 一般我们在SqlMapConfig文件使用typeAliases标签对整个包取别名,然后对resultType赋值 |
MyBatis支持的别名别名:
| 别名 | Java中的数据类型 |
|---|---|
| _byte | byte |
| _long | long |
| _short | short |
| _int | int |
| _integer | int |
| _double | double |
| _float | float |
| _boolean | boolean |
| string | String |
| byte | Byte |
| long | Long |
| short | Short |
| int | Integer |
| integer | Integer |
| double | Double |
| float | Float |
| boolean | Boolean |
| date | Date |
| decimal | BigDecimal |
| bigdecimal | BigDecimal |
| map | Map |
在Mapper.xml文件中我们有时候会给类或者包取别名
3.4.5、占位符#{} 和 ${}(面试会问) #{}
#{}表示一个占位符号,就和JDBC中sql语句中的?一样
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换.
#{}可以接收简单类型值或pojo属性值。
如果 parameterType传输单个简单类型值,#{}括号中可以是value或其它名称
#{}可以有效防止sql注入
${}表示告诉mybatis使用 包 含 的 字 符 串 替 换 所 在 位 置 。 使 用 S t a t e m e n t 把 s q l 语 句 包含的字符串替换所在位置。使用Statement把sql语句 包含的字符串替换所在位置。使用Statement把sql语句{}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
- 通过${}可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换${}可以接收简单类型值或pojo属性值。
如果parameterType传输单个简单类型值(例如 String 类型),${}括号中只能是 value
日志中使用#时sql语句的结果:select * from employees where employeeNumber=?
日志中使用$时sql语句的结果:select * from employees where employeeNumber=1002
$使用Statement对象执行sql,效率比#使用PrepareStatement对象执行sql要低。
#更安全,使用 存 在 安 全 性 问 题 , 因 为 存在安全性问题,因为 存在安全性问题,因为只是单纯的字符串拼接,所以当我们传入的值为:1002;drop table employees时,sql语句会变成:select * from employees where employeeNumber=1002;drop table employees,这样就是两句sql语句了,会存在一定的安全隐患。
所以我们一般不用 $ ,要使用通常都只是用它来替换表名或列名。
3.4.6、selectOne和selectList| 说明 | |
|---|---|
| selectOne | 查询一条记录 如果使用selectOne查询多条记录则抛出异常:org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70) |
| selectList | 可以查询一条或多条记录 |
04. Mapper动态代理方式开发 4.1、原始Dao开发的流程
- 创建Dao接口实现Dao接口:在里面使用MyBatis访问数据库测试/使用 类:调用Dao接口实现类中的方法使用
Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护 4.3、Mapper动态代理开发规范
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发需要遵循以下规范:
Mapper.xml文件中的namespace属性的值要与Dao接口的类路径相同.
Dao接口方法名和Mapper.xml中定义的每个Content Model(内容模型)的id相同
Dao接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
Dao接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class HouseUser {
private Integer UID; // 这里必须和表中的列名一样
private String uName;
private String uPassWord;
}
2、mapper配置文件
3、Dao接口
package com.atSchool.Dao;
import java.util.List;
import com.atSchool.Pojo.HouseUser;
public interface UserDao {
List queryUser(); // 对应着配置文件中标签id为queryUser的sql语句
HouseUser queryUserById(int id); // 对应着配置文件中标签id为queryUserById的sql语句
void insertUser(HouseUser houseUser);
void updateUser(HouseUser houseUser);
void deleteUser(int id);
}
4、测试类
使用SqlSession类中的getMapper方法,实现动态代理。
package com.atSchool.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;
public class TestDao {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
UserDao mapper = openSession.getMapper(UserDao.class);
HouseUser queryUser = mapper.queryUserById(10);
System.out.println(queryUser);
List queryUser2 = mapper.queryUser();
for (HouseUser houseUser : queryUser2) {
System.out.println(houseUser);
}
}
}
05. 封装MyBatis输出结果 5.1、resultType
resultType指定sql语句执行完毕的输出结果类型,sql语句执行完毕后,数据转为Java对象。
返回的类型可以是:
简单类型
对象类型
Map:其中Map的key是表的列名,Map的value是表的列值
返回结果类型为Map时,结果只能有一条,如果是多条结果则会报错。
resultType可以指定将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
resultMap可以自定义表列名和Java类中属性的对应关系。即如果sql查询字段名和pojo类的的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还需要将查询结果集映射到pojo对象中。 resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
使用
在mapper文件中定义
在mapper文件中的sql标签中引用
resultType
查询出的字段在相应的pojo中必须有和它相同的字段对应,或者基本数据类型
适合简单查询
resultMap
需要自定义字段,或者多表查询,一对多等关系,比resultType更强大
适合复杂查询
5.4、pojo类中属性与表列名不一致处理方式- 使用resultMap使用resultType + sql中的取别名
06. QueryVo
开发中通过可以使用pojo传递查询条件.
但是查询条件可能是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如查询用户信息的时候,将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数.
包装对象:Pojo类中的一个属性是另外一个pojo.
需求:根据用户名模糊查询用户信息,查询条件放到QueryVo的user属性中(vo:viewObject)。
QueryVo类
package com.atSchool.Pojo;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QueryVo {
private HouseUser houseUser;
}
mapper映射文件中
select * from sys_user WHERe uName like "%${houseUser.uName}%"
UserDao(dao接口)中
ListqueryUserByName(QueryVo queryVo);
测试类
package com.atSchool.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;
import com.atSchool.Pojo.QueryVo;
public class TestDao {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
UserDao mapper = openSession.getMapper(UserDao.class);
HouseUser houseUser = new HouseUser();
houseUser.setuName("张");
List queryUserByName = mapper.queryUserByName(new QueryVo(houseUser));
for (HouseUser houseUser2 : queryUserByName) {
System.out.println(houseUser2);
}
}
}
07. 动态SQL 7.1、简介
通过mybatis提供的各种标签方法实现动态拼接sql。
例如下面的语句,如果like后面的"%张%"查询条件是空的,还是能够查询出来的;但是如果前面的UID<10查询条件是空的话,则不能查询到结果,所以我们这里需要使用到动态SQL。
SELECT * FROM `sys_user` WHERe UID<10 AND uName LIKE "%张%"7.2、基础使用
| 动态SQL常用标签 | 说明 |
|---|---|
| where(主要是用来简化SQL语句中where条件判断的,能智能的处理and or,不必担心多余导致语法错误) | |
| if语句(简单的条件判断) | |
| 定义一个sql语句,一般和include标签搭配使用 | |
| include(将重复的sql语句提取出来,配合mapper.xml中的sql标签使用) | |
| foreach(在实现MyBatis in语句查询时特别有用) |
使用动态sql后:
7.3、使用include提取SQL语句中相同的部分SELECT * FROM `sys_user` AND UID #{houseUser.id} AND uName LIKE "%${houseUser.name}%"
7.4、使用foreach标签select * from sys_user WHERe UID=#{uid}
需求:
之前我们都是以传参/动态sql的方式对sql语句进行处理,但是如果遇到了IN的查询条件,就只能使用foreach进行处理了。例如以下语句:
SELECT * FROM `sys_user` WHERe UID IN (1,3,4,11)
1、在QueryVo中添加一个集合,用于存储sql语句IN后面的条件值
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QueryVo {
private HouseUser houseUser;
private List ids; // sql语句中有in条件时用到
}
2、在mapper.xml文件中添加
SELECT * FROM `sys_user` WHERe UID IN #{item}
3、在UserDao接口中添加相应的方法(动态代理)
// 根据多个id查询 ListqueryUserByIds(QueryVo queryVo);
3、开始测试
package com.atSchool.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.atSchool.Dao.UserDao;
import com.atSchool.Pojo.HouseUser;
import com.atSchool.Pojo.QueryVo;
public class TestDao {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
UserDao mapper = openSession.getMapper(UserDao.class);
List asList = Arrays.asList(1, 2, 3, 11);
List queryUserByIds = mapper.queryUserByIds(new QueryVo(null, asList));
for (HouseUser houseUser : queryUserByIds) {
System.out.println(houseUser);
}
}
}
08. MySQL自增主键返回 需求
由于有些表的id字段是自增的,所以当添加一条新的数据后可能会需要知道这个新增数据的自增字段的值是多少。
SQL语句
SELECT LAST_INSERT_ID() 实现
SELECT LAST_INSERT_ID() INSERT INTO `sys_user` (uName,uPassWord) VALUES (#{uName},#{uPassWord})
public static void addUser() {
// 4. 创建`SqlSession`对象
SqlSession openSession = Tools.getSqlSessionion();
// 5. 执行`SqlSession`对象执行插入
HouseUser houseUser = new HouseUser("Maria", "123456");
int insert = openSession.insert("insertUser", houseUser);
System.out.println("受影响的行数为:" + insert);
// 6. 提交
openSession.commit();
System.out.println(houseUser);
// 7. 释放资源
openSession.close();
}
09. 控制台打印日志 1、使用mybatis自带的
在SqlMapConfig配置文件中加入
2、Log4j的使用
日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。
使用时需要添加一个log4j.properties文件。就和学习Hadoop时一样
log4j.rootLogger=debug, stdout, R log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # Pattern to output the caller's file name and line number. log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.File=./log/Gosion.log log4j.appender.R.MaxFileSize=100KB # Keep one backup file log4j.appender.R.MaxBackupIndex=5 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
10、SqlMapConfig文件中一次性指定多个mapper
之前在SqlMapConfig文件中指定mapper都是一个一个指定的,但其实也可以一次性指定。
之前:
之后:
使用该方式的前提条件:
- mapper文件所在位置必须和接口在同一包下。mapper文件的名字必须和接口的名字一样。maven默认只识别resource中的资源文件,所以要使用该方式还得在SqlMapConfig文件中使用资源插件告诉maven,mapper文件所在包中的文件也要识别。
11. 关联查询
表说明:
user表
orders表
表对应的pojo类:
package com.atSchool.Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private Integer id;
private String username;
private String birthday;
private String sex;
private String address;
}
package com.atSchool.Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
private Integer id;
private String userId;
private String number;
private String creatTime;
private String note;
}
11.1 一对一查询
写在前面:如果数据库换了的话要记得在配置文件中修改连接到的数据库。
一对一查询:例如订单关联用户,是一对一的关系。
SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id11.1.1 实现方式一:使用ResultType
思路:
定义专门的pojo类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。
实现:
1、根据查询结果创建一个pojo类,用来存储返回的数据package com.atSchool.Pojo;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class OrderUser extends Order {
private String uName;
private String birthday;
private String sex;
private String address;
public OrderUser() {
}
public OrderUser(Integer id, Integer userId, String number, String creatTime, String note, String uName,
String birthday, String sex, String address) {
super(id, userId, number, creatTime, note);
this.uName = uName;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
// 这里还需要将父类的信息加上,否则,遍历的时候,数据会不全
@Override
public String toString() {
return "OrderUser [uName=" + uName + ", birthday=" + birthday + ", sex=" + sex + ", address=" + address
+ ", toString()=" + super.toString() + "]";
}
}
2、创建一个mapper
SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id
注意:
- 别忘了在SqlMapConfig.xml中添加相应的mapper。注意这里返回的类型,type中的类名都可以直接写类名,是因为在SqlMapConfig.xml中统一取别名了
package com.atSchool.Dao;
import java.util.List;
import com.atSchool.Pojo.OrderUser;
public interface OrderUserDao {
List queryOrderUser();
}
4、开始测试
// 测试OrderUser中的查询
public static void teseForOrderUser() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
List queryOrderUser = mapper.queryOrderUser();
for (OrderUser orderUser : queryOrderUser) {
System.out.println(orderUser);
}
}
11.1.2 实现方式二:使用ResultMap
思路:
在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息.
实现:
1、在Order类中加入User属性package com.atSchool.Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
private User user;
private Integer id;
private Integer userId;
private String number;
private String creatTime;
private String note;
}
2、使用resultMap标签中的association进行关联
3、在Dao接口中添加相应的方法SELECT o.id,o.user_id,o.number,o.createtime,o.note,u.username,u.birthday,u.sex,u.address FROM `orders` o LEFT JOIN `user` u ON u.id=o.user_id
List4、开始测试queryOrderUser2();
public static void teseForOrderUser() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
List queryOrder = mapper.queryOrderUser2();
for (Order orderUser : queryOrder) {
System.out.println(orderUser);
}
}
11.2 一对多查询
一对多查询:例如用户关联订单,是一对多的关系。
SELECT u.id,u.username,u.birthday,u.sex,u.address,o.number,o.createtime,o.note FROM `user` u JOIN `orders` o ON u.id=o.user_id1、在User中添加List
package com.atSchool.Pojo;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private Integer id_inUser;
private String uName;
private String birthday;
private String sex;
private String address;
private List orders;
}
2、使用resultMap标签中的collection进行关联
注意:这里有一个易错的经典错误SELECT u.id,u.username,u.birthday,u.sex,u.address,o.id,o.number,o.createtime,o.note FROM `user` u JOIN `orders` o ON u.id=o.user_id
错误:一对多查询,明明日志信息显示查询到的数据条数是对的,但是一打印,list中的数量总是只有一条!
出错原因:因为返回的列没有用于区分权限的id,导致mybatis不知道如何区分,于是把每一条记录都映射成了一个对象。
解决方法:
在连接的两张表中将两张表的id全部查询出来。
这里可能又会出现另外一个错误:当连接的两张表中的id字段名相同时,查询出来的结果中可能会出现如下情况:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
此时我们在resultMap中取别名时就得注意了,column属性的值就得是和查询结果一样的字段名,如我的为id(1)。
3、在Dao接口中添加相应的方法List4、开始测试queryOrderUser3();
public static void teseForOrderUser() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
OrderUserDao mapper = openSession.getMapper(OrderUserDao.class);
List queryOrderUser3 = mapper.queryOrderUser3();
for (User user : queryOrderUser3) {
System.out.println(user);
for (Order order : user.getOrders()) {
System.out.println(order);
}
System.out.println("=======================");
}
}
11.3 嵌套查询
嵌套查询:例如员工表中除了老板,其中主管、小组组长、组员等都是员工。当我们想要查询该员工所有下级时这个时候就可以用嵌套查询。
11.3.1 表结构 11.3.2 需求查询出某个人下的所有员工(递归查询)。
11.3.3 实现 1、创建pojo类package com.atSchool.Pojo;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Employees {
private String id; // 员工编号
private String lastName; // 姓
private String firstName; // 名
private String reportsTo; // 上级编号
private List emps;
}
2、创建Dao接口
package com.atSchool.Dao;
import java.util.List;
import com.atSchool.Pojo.Employees;
public interface EmployeesDao {
List queryReportToById(String id);
}
3、创建mapper_employees.xml资源文件
SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees` WHERe employeeNumber=#{id1} SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees` WHERe reportsTo=#{id2}
解析:
| collection中的参数 | 说明 |
|---|---|
| select | 另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。 |
| column | 将主查询中列的结果作为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将作为嵌套查询的参数。 |
| fetchType | 数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载。 如果要使用延迟加载,除了将fetchType设置为lazy,还需要注意全局配置aggressiveLazyLoading的值应该为false。这个参数在3.4.5版本之前默认值为ture,从3.4.5版本开始默认值改为false。 MyBatis提供的lazyLoadTriggerMethods参数,支持在触发某方法时直接触发延迟加载属性的查询,如equals()方法。 |
执行步骤:
- id为queryReportToById的select语句先执行,返回的数据存储在collection中映射的pojo对应的属性中(注意,此时还没用到collection);紧接着执行collection关联的 id 为select2的语句,将返回的数据全部存储到select2指定的resultMap中。此时其中的collection又会进一步执行与之关联的sql语句,直到全部查完为止。
结果解析:
结果应当是一个严格的树的形状,根是我们要查询下级的人的所有信息,其他部分即为不同等级的他的手下(存储在pojo类中的emps中)。更详细的解析请看测试中的结果说明。 4、在SqlMapConfig.xml文件中注册mapper 5、开始测试
public static void teseForEmployees() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
EmployeesDao mapper = openSession.getMapper(EmployeesDao.class);
List queryReportToById = mapper.queryReportToById("1056");
Employees employees = queryReportToById.get(0); // 整个queryReportToById中只有一个Employees对象,该对象中的emps中嵌套了很多的Employees对象
List emps = employees.getEmps(); // 获取所有的下级对象
for (Employees employees2 : emps) {
System.out.println(employees2);
}
}
结果:
Employees(id=1088, lastName=Patterson, firstName=William, reportsTo=1056, emps=[Employees(id=1611, lastName=Fixter, firstName=Andy, reportsTo=1088, emps=[]), Employees(id=1612, lastName=Marsh, firstName=Peter, reportsTo=1088, emps=[]), Employees(id=1619, lastName=King, firstName=Tom, reportsTo=1088, emps=[])]) Employees(id=1102, lastName=Bondur, firstName=Gerard, reportsTo=1056, emps=[Employees(id=1337, lastName=Bondur, firstName=Loui, reportsTo=1102, emps=[]), Employees(id=1370, lastName=Hernandez, firstName=Gerard, reportsTo=1102, emps=[]), Employees(id=1401, lastName=Castillo, firstName=Pamela, reportsTo=1102, emps=[]), Employees(id=1501, lastName=Bott, firstName=Larry, reportsTo=1102, emps=[]), Employees(id=1504, lastName=Jones, firstName=Barry, reportsTo=1102, emps=[]), Employees(id=1702, lastName=Gerard, firstName=Martin, reportsTo=1102, emps=[])]) Employees(id=1143, lastName=Bow, firstName=Anthony, reportsTo=1056, emps=[Employees(id=1165, lastName=Jennings, firstName=Leslie, reportsTo=1143, emps=[]), Employees(id=1166, lastName=Thompson, firstName=Leslie, reportsTo=1143, emps=[]), Employees(id=1188, lastName=Firrelli, firstName=Julie, reportsTo=1143, emps=[]), Employees(id=1216, lastName=Patterson, firstName=Steve, reportsTo=1143, emps=[]), Employees(id=1286, lastName=Tseng, firstName=Foon Yue, reportsTo=1143, emps=[]), Employees(id=1323, lastName=Vanauf, firstName=George, reportsTo=1143, emps=[])]) Employees(id=1621, lastName=Nishi, firstName=Mami, reportsTo=1056, emps=[Employees(id=1625, lastName=Kato, firstName=Yoshimi, reportsTo=1621, emps=[])])
解析说明:
这里没有打印根节点的信息,即这些都是id为1056的员工的直系下属,其中emps属性中存储的是这些下属的下属。
注意:如果数据库更改了的话就需要修改一下配置文件
12. pagehelper分页
首先要在pom.xml中配置PageHelper的依赖在https://mvnrepository.com/中可以发现 pagehelper有4.x和5.x两个版本,用法有所不同,并不是向下兼容,在使用5.x版本的时候可能会报错。这里我们使用4.2.1版本的。
1、导包在maven仓库中查找,选择github的
2、在SqlMapConfig.xml文件中配置com.github.pagehelper pagehelper 4.2.1
| 配置项 | 说明 |
|---|---|
| dialect | 标识是哪一种数据库,设计上必须。 |
| offsetAsPageNum | 将RowBounds第一个参数offset当成pageNum页码使用 |
| rowBoundsWithCount | 设置为true时,使用RowBounds分页会进行count查询 |
| reasonable | value=true时,pageNum小于1会查询第一页,如果pageNum大于pageSize会查询最后一页 |
注:上面的配置只针对于pagehelper4.x版本的.
3、开始使用pagehelper在mapper文件中添加语句
SELECT employeeNumber,lastName,firstName,reportsTo FROM `employees`
在Dao接口中添加相应的方法
ListqueryEmployees();
开始使用
public static void teseForEmployees() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSession openSession = new SqlSessionFactoryBuilder().build(resourceAsStream).openSession();
EmployeesDao mapper = openSession.getMapper(EmployeesDao.class);
// 1、获取需要的页数和每页数据的条书
// 正常应该是从页面获取到的数据的,这里就直接给出方便测试
int pageNum = 1;
int pageSize = 10;
// 2、开启分页
PageHelper.startPage(pageNum, pageSize);
// 3、查询数据
List queryEmployees = mapper.queryEmployees();
// 4、将查询到的数据传给PageInfo
PageInfo pageInfo = new PageInfo(queryEmployees);
// 5、可以开始使用了
System.out.println(pageInfo); // 打印pageInfo,里面封装了很多分页的信息
List list = pageInfo.getList();
for (Object object : list) {
System.out.println(object);
}
}
| 属性 | 说明 |
|---|---|
| pageNum | 当前页 |
| pageSize | 每页的数量 |
| size | 当前页的数量 |
| orderBy | 排序 |
| startRow | 当前页面第一个元素在数据库中的行号 |
| endRow | 当前页面最后一个元素在数据库中的行号 |
| total | 总记录数 |
| pages | 总页数 |
| list | 结果集 |
| prePage | 前一页 |
| nextPage | 下一页 |
| isFirstPage | 是否为第一页 |
| isLastPage | 是否为最后一页 |
| hasPreviousPage | 是否有前一页 |
| hasNextPage | 是否有下一页 |
| navigatePages | 导航页码数 |
| navigatepageNums | 所有导航页号 |
| navigateFirstPage | 导航第一页 |
| navigateLastPage | 导航最后一页 |
| firstPage | 第一页 |
| lastPage | 最后一页 |



