Mybatis
mybatis
一,常用框架
Struts1 Struts2 SpringMVC框架,类似于servlet,用来处理请求和给出响应
Springmvc spring的一个分支, mvc框架,类似于servlet,Struts2,处理请求和给出响应,可以替代 struts2
Hibernate 持久化框架, 类似于jdbc,连接数据库,操作数据库中表的数据,orm框架
Mybatis 持久化框架,类似于jdbc,hibernate,连接数据库,半自动化的ORM框架
mybatis的sql语句自己编写,sql灵活性较高,便于开发。
Mybatis-plus 是mybatis的工具库,是mybatis的升级,可以对单表进行自动生成sql实现各种功能
JPA可以自动生成sql语句。
MyBatis和Hibernate区别
Hibernate<是一个标准orm框架>是一个标准orm框架>(对象关系映射)。
- 入门门槛较高的,不需要程序写Sql,Sql语句自动生成,对Sql语句进行优化、修改比较困难的;
- 应用场景<适用与需求变化不多的中小型项目>适用与需求变化不多的中小型项目>,比如<后台管理系统>后台管理系统>,erp、crm、oa...;
MyBatis<专注于sql本身>专注于sql本身>,需要程序员自己编写Sql语句,Sql修改、优化比较方便。
- MyBatis是一个不完全的ORM框架,虽然程序员自己写Sql,MyBatis也可以实现映射(输入映射、 输出映射);
- 应用场景<适用与需求变化较多的项目>适用与需求变化较多的项目>,比如<互联网项目>互联网项目>;
Spring 粘合剂,用来简化其他框架的使用及紧密的结合
Springboot 微服务框架
SSH (struts2 + spring + hibernate)
SSM (springmvc + spring + mybatis)
SM(springboot + mybatis + mybatis-plus)
二,mybatis概述
1. JDBC编程存在的问题
JDBC查询数据代码
JDBC添加数据代码
JDBC问题总结如下
- 数据库连接使用时就创建,不使用时便立即释放,从而对数据库进行频繁的操作,导致资源的浪费、影响性能。
优化设想<使用数据库连接池管理数据库对象>使用数据库连接池管理数据库对象>。
- sql都是硬编码到Java程序中,如果改变sql,那么得重新编译Java代码,不利于系统后期的维护。
优化设想<将sql语句配置在xml中>将sql语句配置在xml中>,即使改变sql也不用重新编译源代码。
- 向PreparedStatement设置参数,也是硬编码到Java程序中,不利于后期的维护。
优化设想<将sql语句以及占位符和参数全部配置在xml中>将sql语句以及占位符和参数全部配置在xml中>,改动也不需要重新编译源代码。
-
从resultset遍历结果集数据时,也存在硬编码,不利于后期系统的维护。
优化设想<将查询的结果集自动映射成java对象>将查询的结果集自动映射成java对象>;
针对以上问题,顺其自然的就出现了许多优化JDBC的方案,也就是后期出现的ORM持久层框架,例如 Mybatis以及Hibernate等等;这也就是为什么在实际开发中都比较喜欢用ORM框架的原因了。
2. ORM介绍
在实际开发过程中,我们一般使用ORM框架来代替传统的JDBC,例如Mybatis或者Hibernate,但JDBC 是Java用来实现数据访问的基础,掌握它对于我们理解Java的数据操作流程很有帮助。
ORM<对象关系映射>对象关系映射>(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与 关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映 射的元数据,将程序中的对象自动持久化到关系数据库中。
Java典型的ORM中间件有
ORM技术特点:
- 提高了开发效率,由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所 以我们实际可能已经不需要一个专用的、庞大的数据访问层。
- ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。
3. 什么是MyBatis
MyBatis 本是 Apache 的一个开源项目 iBatis,2010年这个项目由Apache Software Foundation迁移到 了Google Code,并且改名为MyBatis。
iBATIS一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架,iBATIS提供的持久层框
架包括SQL Maps和Data Access Objects(DAOs)。
MyBatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注 SQL本 身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结 果集检索等JDBC繁杂的过程代码。
MyBatis通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 Java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由MyBatis框架执行sql并将结果 映射成Java对象并返回。
4. MyBatis的优点
简单易学:
- 本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件 + 配置几个sql映射文件, 易于学习,易于使用。
- 通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:
- Mybatis不会对应用程序或者数据库的现有设计强加任何影响,sql写在xml里,便于统一管理和优 化。
- 通过sql语句可以满足操作数据库的所有需求。
解除sq与程序代码的耦合:
- 通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测 试。
- sql和代码的分离,提高了可维护性。
提供丰富且强大的标签:
- 提供映射标签,支持对象与数据库的orm字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
5. MyBatis架构
1、MyBatis配置:
- mybatis-config.xml(名称不固定),此文件作为MyBatis的全局(核心)配置文件,配置了 MyBatis的运行环境等信息。
- mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在 mybatisconfig.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编程中对结果的解析处理过程。
三,mybatis的入门案例
- 创建java项目,导入mybatis 和 mysql的jar包
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
- 创建数据库和表,并编写java的实体类
public class User {
private long id;
private String name;
private String sex;
private long age;
// 省略了公共的get和set方法
}
- 导入mybatis的核心配置文件 (resources)
在src/main/resources目录下创建mybatis-config.xml
作用<配置了数据源>配置了数据源>、事务等MyBatis运行环境等
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
<environments default="aa">
<!-- 当前环境 id : 环境名称 唯一 -->
<environment id="aa">
<!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
<transactionManager type="JDBC" />
<!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdb" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 引入映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 再映射文件中编写映射数据
再resources的文件下创建一个mapper文件夹,把映射文件放入进去
建议名称: xxxMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace: 命名空间,作用是mapper文件进行分类管理,用于隔离sql语句。
注意:如果使用mapper代理的方式进行开发,namespace有特殊的作用
-->
<mapper namespace="UserMapper">
<!-- 专门用来封装自定义的sql语句 -->
<!--
通过<select>标签编写查询语句
id: 映射文件中SQL语句的唯一标识。
mybatis会将SQL语句封装到MappedStatement对象中,所以此处的id也可以标识
MappedStatement对象的id;
注意:同一个mapper文件中id不能重复,而且id在mapper代理模式下有着重要作用;
parameterType: 输入参数的类型。
sql语句的占位符:#{};
#{empno}:其中empno表示接收输入的参数值,参数名称为empno;
但是如果参数的类型为简单类型(基本数据类型、包装类、字符串类型),参数名称可以任意
指定;
resultType: 输出参数的类型。
需要指定输出数据为Java中的数据类型(实体类的全限定名)
-->
<select id="selectCount" resultType="int">
select count(1) from user
</select>
<select id="selectAll" resultType="cn.hd.entity.User">
select * from user
</select>
去除XML黄色背景:https://blog.csdn.net/u010318957/article/details/72459183
- 加载映射文件
需要再mybatis的核心配置文件中读取该映射文件
<!-- 引入映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
- 测试
public class TestMybatis {
@Test
public void t1() throws Exception{
// 1,读取mybatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 2,使用sqlSessionFctoryBuild来创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 3,使用工厂对象来获取sqlSession对象 (类似与preparedStatement)
SqlSession sqlSession = factory.openSession();
// 4,使用sqlsession对象来调用其映射文件中的自定义的sql语句执行即可
/**
* 查询单条记录
* selectOne(String statementId, Object param)
* 参数1:映射文件中的statementId,命名空间名.statementId
* 参数2:向sql语句中传入的数据,注意:传入的数据类型必须与映射文件中配置的
parameterType保持一致
* 返回值:就是映射文件中配置的resultType的类型
* 查询多条记录
* selectList()
*/
int count = sqlSession.selectOne("UserMapper.selectCount");
System.out.println("数据总条数:"+ count);
// 5,释放资源
sqlSession.close();
}
// 查询所有的数据
@Test
public void test3() throws Exception{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
List<User> list = session.selectList("mapper.UserMapper.selectAll");
System.out.println(list.toString());
}
}
四,项目优化
1. 通用的MybatisUtils工具类
SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder是MyBatis中用于创建SqlSessionFactory的构建器,它负责解析 mybatis-config.xml配置文件中的配置信息,并创建出SqlSessionFactory对象。
- 解析mybatis-config.xml配置文件 SqlSessionFactoryBuilder首先读取mybatis-config.xml配置文件,解析其中的配置信息。
- 创建Configuration对象 SqlSessionFactoryBuilder创建Configuration对象,并将解析出的配置信息设置到 Configuration对象中。
- 创建SqlSessionFactory对象 SqlSessionFactoryBuilder根据Configuration对象创建SqlSessionFactory对象,并将其返回 给调用者。
- 返回SqlSessionFactory对象 SqlSessionFactoryBuilder将创建出的SqlSessionFactory对象返回给调用者,供应用程序使 用
- SqlSessionFactoryBuilder一般只在应用程序初始化时使用一次,创建出SqlSessionFactory对象 后,就不再需要SqlSessionFactoryBuilder对象
- 用过即丢,推荐作用域范围<方法体内>方法体内>
SqlSessionFactory
SqlSessionFactory
是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜 像。- 每一个MyBatis的应用程序都以一个
SqlSessionFactory
对象的实例为核心。 SqlSessionFactory
也是线程安全的,SqlSessionFactory
一旦被创建,应该在应用执行期间 都存在。在应用运行期间不要重复创建多次,建议使用单例模式
SqlSession
SqlSession
是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection
。- 每个线程都应该有它自己的
SqlSession
实例。SqlSession
的实例不能被共享,同时SqlSession
也是线程不安全的。 - 使用完
SqlSeesion
之后关闭Session很重要 - 线程级,一个request一个
SqlSeesion
总结: 使用单例模式来创建唯一的sqlSessionFactory对象,提高mybatis的执行效率
// 单例模式 : 该类永远只能创建唯一的一个对象
public class MyFactoryUtil {
// 1,定义一个静态的,私有的 sqlSessionFactory对象
private static SqlSessionFactory factory = null;
// 2,提供静态的代码块,来创建唯一的工厂对象
static{
try { // ctrl + alt + T
InputStream is = Resources.getResourceAsStream("mybatis-
config.xml");
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
// 3,提供一个公共的静态的方法,来返回factory的唯一对象
public static SqlSessionFactory getInstance(){
return factory;
}
}
2. 优化连接数据库的四大组件
MyBatis的全局配置文件mybatis-config.xml,配置内容顺序如下:
- properties(属性)
- settings(全局配置参数)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理)
- dataSource(数据源)
- environment(环境子属性对象)
- mappers(映射器)
方式一:
<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
<environments default="aa">
<!-- 当前环境 id : 环境名称 唯一 -->
<environment id="aa">
<!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
<transactionManager type="JDBC" />
<!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatisdb" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
缺点<直接把四大组件信息全部拼接再value中>直接把四大组件信息全部拼接再value中>,不利于重用
<!-- 自定义全局的属性 -->
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="user" value="root"/>
</properties>
<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
<environments default="aa">
<!-- 当前环境 id : 环境名称 唯一 -->
<environment id="aa">
<!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
<transactionManager type="JDBC" />
<!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatisdb" />
<property name="username" value="${user}" />
<property name="password" value="root" />
</dataSource>
</environment>
<environment id="bb">
<!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
<transactionManager type="JDBC" />
<!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url"
value="jdbc:mysql://localhost:3306/springdemo" />
<property name="username" value="${user}" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
缺点: 定义为全局的属性,不利于后期的修改和维护
方式三:
把四大组件信息,全部提取出来,放在一个db.properties的文件中,再mybatis的核心配置文件引 入,并使用${key}来动态的引入,便于后期的更改和维护.
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///mybatisdb
user=root
password=root
mybatis.xml
<!-- 引入外部文件db.properties -->
<properties resource="db.properties"/>
<!-- 连接数据库的环境配置 default : 默认使用的环境名称 -->
<environments default="aa">
<!-- 当前环境 id : 环境名称 唯一 -->
<environment id="aa">
<!-- 使用JDBC的事务管理 type : 事务管理器类型 -->
<transactionManager type="JDBC" />
<!-- 使用JDBC的连接池 type : pooled 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
3. 优化实体类再映射文件中的使用
方式一:
直接再映射文件中的resultType中定义实体类的全类名(包名.类名)
<!-- 查询所有的数据 List<User> -->
<select id="selectAll" resultType="cn.hd.entity.User">
select * from user
</select>
缺点: 全类名每一次都直接引入,编写较为麻烦
方式二:
再mybatis的核心配置文件中,使用typeAliases来为每一个实体类取别名
<!-- 取别名 -->
<typeAliases>
<!-- type : 全类名 alias : 别名-->
<typeAlias type="cn.hd.entity.User" alias="user"/>
</typeAliases>
缺点: 要为每一个实体类取别名,如果实体类较多,则取别名就较为麻烦
方式三:
再mybatis的核心配置文件中,可以采用以扫描包的形式来取别名.
TIP实体类一定要放在统一的包中
POJO
一般只有属性字段,无参构造以及get和set方法,也是指那些没有从任何类继承、也没有实现任何接 口,更没有被其它框架侵入的Java对象。因此它特别灵活可扩展,可以实现让一个模型贯穿多个层,简 单来说可以理解成不包含业务逻辑的单纯用来存储数据的Java类。
Entity<实体类对象>实体类对象>。
实体和数据表一一对应,一个实体一张表。
注<关于区分pojo和entity>关于区分pojo和entity>,查了一些资料也没发现有特别大的差别,功能上来说差别不大,只能从含 义上大致区分一下。不过现在大都是用entity来作为一个表映射的类。
<!-- 取别名 -->
<typeAliases>
<!-- 扫描包,会把该包中的所有的类全部自动取别名,别名为类名(首字母小写) -->
<package name="cn.hd.entity"/>
</typeAliases>
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 | object | java.lang.Object |
4. 引入log4j日志
导入log4j的jar包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
再resources中引入log4j的配置文件
log4j.properties
#井号表示注释,配置内容为键值对格式,每行只能有一个键值对,键值对之间以=连接
#指定logger
#设定log4j的日志级别和输出的目的地
#INFO日志级别,Console和logfile输出的目的地
#等级 OFF,ERROR,WARN,INFO,DEBUG,TRACE,ALL
log4j.rootLogger=DEBUG,CONSOLE,file
log4j.logger.cn.smbms.dao=debug
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
#指定appender
#设定Logger的Console,其中Console为自定义名称,类型为控制台输出
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
################################################################################
######
# %d 输出日志的日期和时间,指定格式:%d{yyyy-MM-dd HH:mm:ss SSS}
# %p 输出的日志级别
# %c 输出所属类的全类名
# %M 方法名
# %m 输出代码中指定消息
# %n 一个换行符
################################################################################
######
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L)
%m%n
log4j.logger.com.opensymphony.xwork2=error
再核心配置文件中使用setting来引入log4j
<!-- 引入log4j日志 -->
<settings>
<setting name="logImpl" value="LOG4J"></setting>
</settings>
/**
* 日志测试
*/
public class LogTest {
/*
* 我们之前想要查看程序的执行信息,都是通过System.out输出,这样显然是不方便的
* 1、内容格式需要自己设置
* 2、项目上线之后,不方便查看
*/
@Test
public void beforeLogTest(){
System.out.println("常规日志输出...");
}
/*
* Java自带的日志输出
* 晚于log4j,用的很少
*/
@Test
public void julTest(){
java.util.logging.Logger logger =
java.util.logging.Logger.getLogger(LogTest.class.getName());
logger.info("jul日志输出...");
}
@Test
public void log4jTest(){
org.apache.log4j.Logger logger =
org.apache.log4j.Logger.getLogger(LogTest.class);
logger.trace("trace日志...");
logger.debug("debug日志...");
logger.info("info日志...");
logger.warn("warn日志...");
logger.error("error日志...");
}
}
5. 引入Mapper代理模式
方式一:原始DAO开发
在session调用sql时,根据namespace+id的字符串形式找到sql语句并执行
缺点:
- 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
- 调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编 译阶段也不报错,不利于程序员开发。
方式二:Mapper代理模式
Mapper代理开发方式只需要程序员编写Mapper接口(相当于Dao接口),由MyBatis框架根据接口定 义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
程序员编写Mapper接口需要遵循一些开发规范,MyBatis可以自动生成Mapper接口实现类代理对象。
开发规范
-
- Mapper.xml文件中的namespace与mapper接口的类路径相同。
-
- Mapper.xml中定义的每个标签的id与Mapper接口方法名相同。
-
- Mapper.xml中定义的每个sql的parameterType的类型与Mapper接口方法的参数类型相同。
-
- Mapper.xml中定义的每个sql的resultType的类型与Mapper接口方法返回值类型相同。
注
- 在java中新创建一个mapper包,并创建接口(xxxMapper.java)。【例如
.hs.mapper】
- 在对应的resource文件夹中创建一个与接口一样的多级文件夹,并创建映射文件,要求映射文件名 必须和接口名重名.(xxxMapper.xml) 。 【例如
/hs/mapper】
创建文件夹
创建映射文件
- 为配置定义接口方式来实现
-
Mapper映射文件的namespace必须是 对应的mapper的接口的全类名
-
Mapper映射文件的id必须对应的接口中的方法名
-
Mapper映射文件的resultType必须对应的时接口中的返回值类型或泛型
-
Mapper映射文件的paramtertype必须对应的时接口中的参数的类型
- 在核心配置文件中配置 映射文件的新读取方式
- 测试:
@Test
public void t2()throws IOException{
SqlSessionFactory factory = MyBatisUitls.getFactory();
SqlSession session = factory.openSession();
StudentMapper mapper=session.getMapper(StudentMapper.class);
List<Student> list = mapper.selectStudents();
for(Student s:list){
System.out.println(s.toString()
)
session.close();
}
6. 对实体类中基本数据类型的优化
如果表中字段时基本数据类型,实体类也是基本数据类型时,如果表中的数据是null,则查询时就 好报错(null不能放入到实体类中的基本数据类型的属性中);
解决<以后所有的实体类中的基本数据类型全部改为其包装类>以后所有的实体类中的基本数据类型全部改为其包装类>
NOTE实体类中的所有的属性名 与 表中的所有的字段名一一对应
五,常用方法
1、mybatis实现常见的CRUD功能
增加 删除 修改 查询所有 根据id查询 模糊查询 多条件查询 查询字段与实体类属性不匹配
注意:
mybaits的占位符和赋值组合 : #{ 属性名 } ? ps.setObject(1,student.getName())
增删改时需要手动提交事务: session.commit(); // 手动提交事务
StudentMappr.java
public interface StudentMapper {
// 查询所有的数据
public List<Student> selectAll();
// 添加数据
public int add(Student student);
// 修改
public int update(Student student);
// 删除
public int del(long id);
// 根据id查询学生信息
public Student selectById(long id);
// 模糊查询
public List<Student> selectByName(String name);
/*
多条件查询: 注意:mybatis默认状态下不能一次性传入多个不同类型的数据
1,多个条件正好是实体类的直接属性 对象
根据姓名模糊,性别具体,年龄具体 查询
2,多个条件不是实体类的直接属性 Map
根据姓名模糊,和年龄范围查询
3,多个条件不是实体类的直接属性 xxxVo
根据年龄范围查询
4,多个条件不是实体类的直接属性 @param方法来实现一次性传入多条不同的数据
根据年龄范围查询
*/
public List<Student> selectByNameAndSexAndAge(Student student);
public List<Student> selectByNameAndAge(Map map);
public List<Student> selectByAge(StudentVo studentVo);
public List<Student> selectByAgeInParam(@Param("min") long
minage,@Param("max") long maxage);
/*
数据库表的字段信息与实体类的属性不匹配
1,sql语句查询时,以取别名的形式实现查询的结果与实体类属性名重名
2,使用resultMap来实现指定查询数据的赋值
*/
}
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.hs.mapper.StudentMapper">
<!-- 使用resultMap来手动的定义 字段之间的赋值 -->
<resultMap id="stumap" type="student">
<result column="s_name" property="name"/>
</resultMap>
<!-- 查询所有的学生信息 -->
<select id="selectAll" resultMap="stumap">
select * from student
</select>
<!--<select id="selectAll" resultType="student">
select id,s_name as name ,age,birthday,address from student
</select>-->
<!-- 添加数据
占位符+赋值: #{ 属性名 }
useGeneratedKeys : 设置当前的主键为自增方式
keyProperty: 设置自增后的主键值为当前的对象的属性赋值
结果: 添加成功后,java中的对象的id会反射到java对象中
-->
<insert id="add" parameterType="student" useGeneratedKeys="true"
keyProperty="id">
insert into student values(null,#{name},#{sex},#{age},#{birthday},#
{address})
</insert>
<update id="update" parameterType="student">
update student set name=#{name},age=#{age},birthday=#
{birthday},address=#{address} where id=#{id}
</update>
<delete id="del" parameterType="long">
delete from student where id = #{id}
</delete>
<select id="selectById" parameterType="long" resultType="student">
select * from student where id = #{id}
</select>
<!--
方式一 : 传入的参数前后加% %张%
方式二 : 再sql语句中实现前后拼接 % '%' #{name} '%'
方式三 : 使用${key}模式来实现前后拼接 '%${name}%'
-->
<!--<select id="selectByName" parameterType="java.lang.String"
resultType="student">
select * from student where name like #{name}
</select>-->
<!--<select id="selectByName" parameterType="java.lang.String"
resultType="student">
select * from student where name like '%' #{name} '%'
</select>-->
<select id="selectByName" parameterType="java.lang.String"
resultType="student">
select * from student where name like '%${name}%'
</select>
<select id="selectByNameAndSexAndAge" parameterType="student"
resultType="student">
select * from student
where name like '%${name}%' and sex=#{sex} and age=#{age}
</select>
<!-- 配置文件中的小于一般不能使用
1.使用between .. and ...
select * from student
where name like '%${name}%' and age between #{minage} and #
{maxage}
2.让占位符和字段进行反转即可
select * from student
where name like '%${name}%' and age >= #{minage} and #{maxage}
>= age
3.使用 < < > > <= <= >= >=
select * from student
where name like '%${name}%' and age > #{minage} and age
< #{maxage}
-->
<select id="selectByNameAndAge" parameterType="java.util.Map"
resultType="student">
select * from student
where name like '%${name}%' and age >= #{minage} and #{maxage} >= age
</select>
<select id="selectByAge" parameterType="studentVo" resultType="student">
select * from student where age between #{minage} and #{maxage}
</select>
<select id="selectByAgeInParam" resultType="student">
select * from student where age between #{min} and #{max}
</select>
</mapper>
2、resultMap的简单用法
resultType可以指定将查询结果映射为实体类,但需要实体类的属性名和SQL查询的列名一致方可映射成功,当然如果开启下划线转驼峰 或 Sql设置列别名,也可以自动映射。
<!-- 开启下划线与驼峰命名的转换:-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- 如果SQL查询字段名和实体类的属性名不一致,可以通过resultMap将字段名和属性名作一个对应 关系,resultMap实质上还会将查询结果映射到实体类对象中。
- resultMap可以实现将查询结果映射为复合型的实体类,比如在查询结果映射对象中包括实体类和 list实现一对一查询和一对多查询。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password from users
</select>
resultMap常用的属性
resultType: 自动映射。
resultMap: 手动映射。
- id: 唯一标识,名称;
- type: 手动映射的java类型;
- 子标签 < id/> 配置数据库表中的主键和实体类中属性的对应关系;
- 子标签 < result/> 配置数据库表中的普通字段和实体类中属性的对应关系;
- property<实体类中的成员变量名>实体类中的成员变量名>;
- column<结果集中的字段名称>结果集中的字段名称>;
- javaType<实体类成员变量的类型>实体类成员变量的类型>,由mybaits自动识别,可不配置;
- jdbcType<表字段的类型>表字段的类型>,由mybaits自动识别,可不配置;
- typeHandler<自定义类型处理器>自定义类型处理器>,用的相对比较少;
在resultMap标签中,可使用和子标签指定一个属性名跟字段名的映射,property是实体类的属性名, column是数据库中的字段名。子节点一般对应该记录行的数据库主键字段,可提升MyBatis性能,二者 作用差不多。
resultMap的自动映射级别:
PARTIAL,默认值,自动匹配基本类型
NONE,不进行自动匹配
FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。
一般设置为自动匹配所有,这样只需要在resultMap中配置名称不对应的属性即可,其他不做手动映射 的属性依然可以映射上。此选项在mybatis-config.xml(核心配置文件)中的settings标签中设置,代码 如下
<!--设置为FULL,自动匹配所有,则在resultMap中不进行匹配的字段也可以映射-->
<setting name="autoMappingBehavior" value="FULL"/>
六,分页
之前的分页都是以mysql中的limit关键字实现分页。Page
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封 装,使用简单的方式即可获得分页的相关数据
开发步骤:
①导入通用PageHelper的坐标
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
②在mybatis核心配置文件中配置PageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 方言 默认是mysql方言 -->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
③测试分页数据获取
@Test
public void fenye(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
PageHelper.startPage(2,2);
List<Student> students = studentMapper.selectAll();
for(Student student : students){
System.out.println(student.toString());
}
// 动态获取分页后相关的数据参数
PageInfo<Student> pageInfo = new PageInfo<>(students);
System.out.println(pageInfo.getTotal()); // 总条数
System.out.println(pageInfo.getPageNum()); // 页码
System.out.println(pageInfo.getPages()); // 总页数
System.out.println(pageInfo.getPageSize()); // 每页展示条数
System.out.println(pageInfo.getList()); // 数据集合
sqlSession.close();
}
注意:
再开发中,如果需要多条件查询并分页,我们只需要提供多条件查询的语句即可,再查询前加入 pageHelper即可,案例如下:
// 多条件查询的语句
<select id="selectByNameAndAage" resultType="student">
select * from student
where name like '%' #{name} '%' and age between #{min} and #{max}
</select>
// 分页测试
@Test
public void home(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
PageHelper.startPage(1,2); // 加入分页即可实现多条件查询并分页效果
List<Student> students = mapper.selectByNameAndAage("无", 10l, 30l);
for(Student student : students){
System.out.println(student.toString());
}
sqlSession.close();
}
七,动态sql
再真实的开发中,会出现根据多条件动态查询,动态的修改,批量删除等功能,mybatis也提供了一套 专门的动态sql来实现这些效果.
动态Sql是指MyBatis对Sql语句进行灵活操作,通过表达式进行判断,对Sql进行灵活拼接、组装。
1. <where>
标签
作用:用于替代 SQL 中的 WHERE 关键字,能够智能地处理 AND/OR 条件。
特点:
- 只有当子元素返回内容时才会插入 WHERE 子句
- 会自动去除开头多余的 AND 或 OR
示例:
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
AND state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
</where>
</select>
2. <if>
标签
作用:条件判断,根据表达式的值决定是否包含其中的 SQL 片段。
特点:
- 使用 OGNL 表达式进行判断
- 通常与其他动态 SQL 标签配合使用
示例:
<if test="title != null">
AND title = #{title}
</if>
3. <set>
标签
作用:用于 UPDATE 语句,智能处理 SET 子句。
特点:
- 自动去除结尾的逗号
- 只有当子元素返回内容时才会插入 SET 子句
示例:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
</set>
where id=#{id}
</update>
4. <foreach>
标签
作用:遍历集合,常用于 IN 条件或批量操作。
属性:
collection
:要遍历的集合item
:当前元素变量名index
:索引变量名open
/close
:开始/结束字符串separator
:元素间分隔符
示例:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT * FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
5. <choose>
、<when>
、<otherwise>
标签
作用:实现类似 Java 中的 switch-case 逻辑。
特点:
<choose>
作为父标签<when>
表示 case 条件<otherwise>
表示默认情况
示例:
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
案例
StudentMapper.java
public interface StudentMapper {
// 根据姓名模糊,年龄范围
public List<Student> selectByNameAndAage(@Param("name")String
name,@Param("min")long minage,@Param("max")long maxage);
// 动态sql的查询
public List<Student> select(Map map);
// 修改
public int update(Student student);
// 批量删除
public int del(List list);
}
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.StudentMapper">
<select id="selectByNameAndAage" resultType="student">
select * from student
where name like '%' #{name} '%' and age between #{min} and #{max}
</select>
<!-- where + if 组合--->
<select id="select" parameterType="java.util.Map" resultType="student">
select * from student
<where>
<if test="name!=null and name!=''">
name like '%${name}%'
</if>
<if test="sex!=null and sex!=''">
and sex = #{sex}
</if>
<if test="minage!=null and minage!=''">
and age between #{minage} and #{maxage}
</if>
</where>
</select>
<!--<update id="update" parameterType="student" >
update student set name=#{name},sex=#{sex},age=#{age},birthday=#
{birthday},address=#{address}
where id=#{id}
</update>-->
<!-- set + if 组合--->
<update id="update" parameterType="student" >
update student
<set>
<if test="name!=null and name!=''"> name=#{name}, </if>
<if test="sex!=null and sex!=''"> sex=#{sex}, </if>
<if test="age!=null and age!=''"> age=#{age}, </if>
<if test="birthday!=null and birthday!=''"> birthday=#{birthday},
</if>
<if test="address!=null and address!=''"> address=#{address} </if>
</set>
where id=#{id}
</update>
<!-- foreach 组合
collection: 遍历的数组或集合对象名称。
SQL只接收一个数组参数,这时SQL解析参数的名称MyBatis固定为array。
SQL只接收一个List参数,这时SQL解析参数的名称MyBatis固定为list。
如果是通过一个实体类或自定义类型的属性传递到SQL的数组或List集合,则参数的名称为实
体类或自定义类型中的属性名。
index: 为数组的下标。
item: 每次遍历生成的对象。
open: 开始遍历时拼接的串。
close: 结束遍历时拼接的串。
separator: 遍历的两个对象中需要拼接的串。
--->
<delete id="del" parameterType="java.util.List">
delete from student where id in
<foreach collection="list" item="i" open="(" close=")" separator=",">
#{i}
</foreach>
</delete>
<!-- choose + when 组合--->
<select id="selectByTJ" parameterType="student" resultType="student">
select * from dept where
<choose>
<when test="id!=null and id!=''">
deptno = #{deptno}
</when>
<when test="name!=null and name!=''">
name like #{name}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</select>
</mapper>
测试:
public class TestMybatis {
@Test
public void del(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List list = new ArrayList();
list.add(3);
list.add(4);
list.add(5);
list.add(6);
int num = mapper.del(list);
sqlSession.commit();
sqlSession.close();
}
@Test
public void update(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setId(1L);
student.setAge(98L);
student.setAddress("河南省商丘市");
int num = mapper.update(student);
sqlSession.commit();
sqlSession.close();
}
@Test
public void select(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
PageHelper.startPage(1,2);
Map map = new HashMap();
//map.put("name","张");
map.put("sex","男");
map.put("minage",20);
map.put("maxage",200);
List<Student> students = mapper.select(map);
for(Student student : students){
System.out.println(student.toString());
}
sqlSession.close();
}
@Test
public void home(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
PageHelper.startPage(1,2);
List<Student> students = mapper.selectByNameAndAage("无", 10l, 30l);
for(Student student : students){
System.out.println(student.toString());
}
sqlSession.close();
}
}
八,sql片段
将实现的动态SQL判断代码块抽取出来,组成一个SQL片段,其它的statement中就通过 < include> 标 签就可以引用SQL片段,方便程序员进行开发。
在项目中有时会发现该表的操作中有很多的多条件查询,每一个条件查询的前半部分是相同,则我们可 以使用sql包含来实现一个编写一次,多次调用的方式。
<!-- 定义一个sql,来定义其通用的查询的前半部分 -->
<sql id="sl">
select id,name,sex,age from student
</sql>
<!-- 在下方的sql语句查询中引用通用的部分sql代码 -->
<select id="selectByLikeName3" parameterType="java.lang.String"
resultType="student">
<include refid="sl"></include>
where name like '%${name}%'
</select>
<select id="selectByNameAndAge" parameterType="student"
resultType="student">
<include refid="sl"></include>
where name like '%${name}%' and age=#{age}
</select>
九,注解开发
Mybatis最初配置信息是基于XML,映射语句(SQL)也是定义在 XML 中的。而到了MyBatis3提供了新 的基于注解的配置。使用注解开发方式,可以减少编写 Mapper 映射文件。
常用注解说明
1. 单表操作
StudentInfoMapper.java
public interface StudentInfoMapper {
@Insert("insert into student values(null,#{name},#{sex},#{age},#{birthday},#
{address})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn =
"id")
public int add(Student student);
@Update("update student set name=#{name},sex=#{sex},age=#{age},birthday=#
{birthday},address=#{address} where id=#{id}")
public int update(Student student);
@Delete("delete from student where id =#{id}")
public int delete(long id);
@Select("select * from student")
public List<Student> selectAll();
}
注意: 注解模式时,也要再核心配置文件中实现扫描包
<mappers>
<package name="com.mapper"/>
</mappers>
Mybatis注解开发:https://blog.csdn.net/weixin\_43883917/article/details/113830667
测试:
public class TestMybatisAnition {
@Test
public void t4(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
PageHelper.startPage(1,2);
StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
mapper.delete(4l);
sqlSession.commit();
sqlSession.close();
}
@Test
public void t3(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
PageHelper.startPage(1,2); // 查询数据并分页
StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
List<Student> students = mapper.selectAll();
for (Student student : students){
System.out.println(student.toString());
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void t2(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
Student student = new Student(4l,"周芷若","女",20l,new Date(),"北京");
mapper.update(student);
sqlSession.commit();
sqlSession.close();
}
@Test
public void t1(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
StudentInfoDao mapper = sqlSession.getMapper(StudentInfoDao.class);
Student student = new Student(1l,"周芷若","女",19l,new Date(),"北京");
mapper.add(student);
sqlSession.commit();
sqlSession.close();
}
}
2. 注解解决字段不重名
使用@Results 和 @Result 来实现 ,类似ResultMap的写法
// 查询的结果字段与实体类的属性不重名
@Select("select id,name as sname,sex,age,birthday,address from student")
@Results(value = {
@Result(column = "sname",property = "name"),
@Result(column = "age",property = "age")
})
3. 注解实现动态sql
使用内部类来实现,方法中返回的是new SQL() , org.apache.ibatis.jdbc.SQL包
// 动态sql(基于内部类来实现) 根据name模糊,根据sex具体
// type : 自定义的内部类 method : 内部类的方法实现
@SelectProvider(type = namesex.class,method = "csql")
public List<Student> selectByNameAndSex(Map map);
// 定义一个内部类,再内部类中定义一个方法( String返回值 map集合参数 )来生成动态sql语句
class namesex{ // select id,name,sex from 表名 where ... and ...
public String csql(Map map){
String sql = new SQL(){{
SELECT(" id,name,sex ");
SELECT("age,birthday,address");
FROM("student");
if(map.get("name")!=null && !"".equals(map.get("name"))){
WHERE(" name like '%${name}%'");
}
if(map.get("sex")!=null && !"".equals(map.get("sex"))){
WHERE(" sex=#{sex}");
}
}}.toString();
return sql;
}
}
升级:
双层sql语句的动态生成案例:
select count(1) from
(select id,name,sex,age,birthday,addressfrom student where ......) s
@SelectProvider(type =cns.class,method ="csql" )
public int selectCountByNS(Map map);
class cns{
public String csql(Map map){
String s1 = new SQL(){{
SELECT(" * ");
FROM("student");
if(map.get("name")!=null){
WHERE("name like '%${name}%'");
}
if(map.get("sex")!=null){
WHERE(" sex = #{sex}");
}
}}.toString();
String s2 = new SQL(){{
SELECT("count(1)");
FROM("("+s1+") as s");
}}.toString();
return s2;
}
}
升级:
根据id批量删除
delete from student where id in (......);
注意: mybatis3.5.5版本以上,内部类的方法中可以传入list集合,以下版本只能传入Map集合
@DeleteProvider( type =delClass.class ,method = "csql")
public int deletes(List list);
class delClass{
public String csql(List list){ // 低版本的以map集合包裹list传入
String sql = new SQL(){{
DELETE_FROM("student");
String s = "id in(";
for(int i=0;i<list.size();i++){
Object obj = list.get(i);
if(i==list.size()-1){
s+=obj+")";
}else{
s+=obj+",";
}
}
WHERE(s);
}}.toString();
return sql;
}
}
4. @MapperScan
在入口处添加@MapperScan
@SpringBootApplication
@MapperScan("fun.masttf.mapper")
public class testApplication {
public static void main(String[] args) {
SpringApplication.run(testApplication.class, args);
}
}
配置说明
- 使用
@MapperScan
的优势:- 简化配置,无需创建 SqlSessionFactory、SqlSessionTemplate 等 bean
- 自动扫描指定包下的所有接口作为 Mapper 组件注册到 Spring 容器
- 与 Spring Boot 的自动配置机制完美结合
@MapperScan 的作用
- Spring 组件注册:
@MapperScan
将指定包下的所有 Mapper 接口自动注册为 Spring Bean,使它们能够被自动装配(@Autowired
)到其他组件中 - 接口实现类生成:MyBatis 会为这些接口动态生成实现类,实现与 XML 文件的绑定
- 批量扫描:一次性指定整个包,避免为每个 Mapper 接口单独添加
@Mapper
注解
XML 中 namespace 的作用
- 映射绑定:将 XML 文件中的 SQL 定义与特定 Java 接口绑定
- 运行时关联:MyBatis 运行时根据这个关联找到对应的 SQL 语句
配置文件迁移到application.yml/properties:
-
传统MyBatis的
mybatis-config.xml
中的核心配置被移至Spring Boot配置文件 -
mybatis: # XML文件位置 mapper-locations: classpath:fun/masttf/mapper/*.xml # 实体类别名包 type-aliases-package: fun.masttf.entity.po # 对应原mybatis-config.xml中的<settings> configuration: # 驼峰命名自动映射 map-underscore-to-camel-case: true # 缓存配置 cache-enabled: true # 延迟加载配置 lazy-loading-enabled: true aggressive-lazy-loading: false # 日志实现 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # MyBatis-Plus特有配置(如果使用) global-config: db-config: id-type: auto table-underline: true
NOTE
需要添加依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency>
十,表与表之间的关系
表之间的关系: 一对一 一对多 多对多
员工表与部门表
- 1,查询员工信息的同时查询其所属的部门信息
- 2,查询部门信息的同时把所属的员工信息也要查询出来
创建表:
create table dept(
did int PRIMARY key auto_increment,
dname varchar(20)
)
insert into dept values(null,"市场部");
insert into dept values(null,"教学部");
insert into dept values(null,"研发部");
create table emp(
eid int PRIMARY key auto_increment,
ename varchar(20),
esex varchar(10),
eage int,
did int ,
foreign key(did) REFERENCES dept(did)
)
insert into emp values(null,"张三丰","男",22,1);
insert into emp values(null,"张无忌","男",20,1);
insert into emp values(null,"赵敏","女",22,2);
insert into emp values(null,"周芷若","女",20,1);
1 一对一
根据员工编号查询员工信息,同时把该员工所属的部门信息也要查询
创建实体类:
public class Emp {
private long eid;
private String ename;
private String esex;
private long eage;
private long did;
// 一对一 查询员工信息的同时把所属的部门信息查到
private Dept dept;
// 省略对应的get和set方法
}
编写EmpDao.java
public interface EmpDao {
// 根据eid来查询员工信息
public Emp selectByEid(long id);
}
EmpDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.EmpDao">
<resultMap id="emp_dept" type="emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="esex" property="esex"/>
<result column="eage" property="eage"/>
<result column="did" property="did"/>
<!-- 一对一 封装 -->
<association property="dept" javaType="dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
</association>
</resultMap>
<!-- 查询的结果中,有emp的属性值则自动封装成对象;当时其所属的部门信息不能自动封装,我们就
可以手动实现封装 -->
<select id="selectByEid" parameterType="long" resultMap="emp_dept">
select emp.*,dept.did ddid,dept.dname from emp,dept where
emp.did=dept.did and eid=#{eid}
</select>
测试:
@Test
public void del(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectByEid(1);
System.out.println(emp.toString());
sqlSession.close();
}
优化一
可以再mybatis的核心配置文件中设置 自动装配 ,resultMap中的手动封装数据就会简化
<setting name="autoMappingBehavior" value="FULL"/>
再说一下resultMap的自动映射级别:
PARTIAL,默认值,自动匹配基本类型
NONE,不进行自动匹配
FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.EmpDao">
<resultMap id="emp_dept" type="emp">
<!-- 一对一 封装 -->
<association property="dept" javaType="dept">
</association>
</resultMap>
<!-- 查询的结果中,有emp的属性值则自动封装成对象;当时其所属的部门信息不能自动封装,我们就
可以手动实现封装 -->
<select id="selectByEid" parameterType="long" resultMap="emp_dept">
select emp.*,dept.did ddid,dept.dname from emp,dept where
emp.did=dept.did and eid=#{eid}
</select>
</mapper>
优化二
可以以单表查询的方法来查询员工信息,在把数据封装中association使用外键来调用另一张表的查询 sql来实现查询对象,并封装到当前的员工信息的dept对象中。
deptDao.java
// 根据did来查询部门信息
public Dept selectByDid(long did);
deptDao.xml
<select id="selectByDid" parameterType="long" resultType="dept">
select * from dept where did=#{did}
</select>
EmpDao.java
// 根据id来查询员工信息及部门信息
public Emp selectByEid2(long id);
EmpDao.xml
<resultMap id="emp_dept_2" type="emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="sex" property="esex"/>
<result column="eage" property="eage"/>
<result column="did" property="did"/>
<!-- 根据外键 去调用另一条查询语句把结果映射到当前的reusltMap中进行 一对一 封装 --
>
<association property="dept" javaType="dept" column="did"
select="com.mapper.DeptDao.selectByDid"></association>
</resultMap>
<select id="selectByEid2" parameterType="long" resultMap="emp_dept_2">
select * from emp where eid = #{eid}
</select>
2 一对多
根据部门编号来查询部门信息,同时把该部门下的所属的所有的员工信息也查询
编写实体类:
public class Dept {
private long did;
private String dname;
// 一个部门下有多个员工信息 一对多
private List<Emp> emps;
// 省略公共的get和set方法
}
编写DeptDao.java
// 根据编号来查询部门信息同时把该部门下的员工信息也要查到
public Dept selectdeByDid(long did);
DeptDao.xml
// 定义手动封装的方式
<resultMap id="dept_emp" type="dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
<!-- collection : 集合 一对多 -->
<collection property="emps" ofType="emp">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="esex" property="esex"/>
<result column="eage" property="eage"/>
<result column="did" property="did"/>
</collection>
</resultMap>
<select id="selectdeByDid" parameterType="long" resultMap="dept_emp">
select emp.*,dname from dept,emp where dept.did=emp.did and dept.did=1
</select>
测试:
@Test
public void sel3(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
DeptDao mapper = sqlSession.getMapper(DeptDao.class);
Dept dept = mapper.selectdeByDid(1);
System.out.println(dept);
sqlSession.close();
}
优化一
可以再mybatis的核心配置文件中设置 自动装配 ,resultMap中的手动封装数据就会简化
<setting name="autoMappingBehavior" value="FULL"/>
再说一下resultMap的自动映射级别:
PARTIAL,默认值,自动匹配基本类型
NONE,不进行自动匹配
FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。
<resultMap id="dept_emp" type="dept">
// 特殊定义did的自动注入,如果不写,则查询的结果可能是多行,封装成一个对象不匹配
<id column="did" property="did"/>
<!-- collection : 集合 一对多 -->
<collection property="emps" ofType="emp">
</collection>
</resultMap>
<select id="selectdeByDid" parameterType="long" resultMap="dept_emp">
select emp.*,dname from dept,emp where dept.did=emp.did and dept.did=1
</select>
优化二
可以以单表查询的方式先查询部门信息,然后再Collection中的select来调用另一条根据did查询所有 的员工信息的sql语句来查询员工信息,进行集合封装。
EmpDao.java
// 根据did查询员工信息
public List<Emp> selectByDid(long did);
EmpDao.xml
<select id="selectByDid" parameterType="long" resultType="emp">
select * from emp where did=#{did}
</select>
deptDao.java
// 根据编号来查询部门信息同时把该部门下的员工信息也要查到
public Dept selectdeByDid2(long did);
deptDao.xml
<resultMap id="dept_emp_2" type="dept">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
<!-- 一对多 colum : 外键 select : 另一条查询语句的坐标 -->
<collection property="emps" ofType="emp"
column="did" select="com.mapper.EmpDao.selectByDid"></collection>
</resultMap>
<select id="selectdeByDid2" parameterType="long" resultMap="dept_emp_2">
select * from dept where did=#{did}
</select>
十一,延迟加载
针对关联查询操作。
assoation 和 collection
延迟加载
(1) 懒加载。
依赖的对象,在不需要时,先不加载。如果需要的对象,则立马执行sql语句进行查询。
(2) 分类:
全局延迟加载
局部延迟加载
开启全局延迟加载
<!--开启延迟加载开关,默认false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--
在延迟加载的基础上使用:
false按需加载 , 访问指定属性,不会加载其他属性;
true 全部加载 , 访问任意属性,都会加载全部属性;
-->
<setting name="aggressiveLazyLoading" value="true"/>
注意<如果使用中去调用了方法去打印信息>如果使用中去调用了方法去打印信息>【toString()】
则需要额外加入方法的配置:
<!--指定哪些方法去触发延迟加载,hashCode,equals,clone,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>
局部延迟加载
- (1)注释全局加载设置
- (2)设置局部延迟加载
<association fetchType="lazy" ...
<collection fetchType="lazy" ...
注意:
局部延迟加载的优先级要高于全局延迟加载
总结:
一对一 一般采用立即加载
一对多 多对多 一般采用懒加载
十二,查询缓存
缓存<将数据临时存储在存储介质>将数据临时存储在存储介质>(内存,文件)中,关系型数据库的缓存目的就是为了减轻数据库的 压力。
数据库的数据实际是存储在硬盘中,如果我们程序需要用到数据,那么就需要频繁的从磁盘中读取数 据,效率低,数据库压力大。我们可以把查询到的数据缓存起来,这样就减少了频繁操作磁盘数据,提 高查询效率,减轻服务器压力。
Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能。但是在实际项目开发中,很少使用 Mybatis的缓存机制,现在主流的缓存机制是redis。
缓存实现技术:
mybatis默认支持内置的缓存技术
redis mongDB
mybatis的缓存:
一级缓存(默认支持)
二级缓存 (配置)
1. 什么是查询缓存
- MyBatis提供查询缓存,用于减轻数据库压力,提高数据库性能。
- MyBatis提供一级缓存和二级缓存。
- 一级缓存是SqlSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一 个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域 (HashMap)是互相不影响的。
- 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个 SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
2. 一级缓存
一级缓存是基于PerpetualCache(MyBatis自带)的 HashMap 本地缓存,作用范围为 session 域内。 当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。
在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 mapper 的方法,往往 只执行一次 SQL。因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,再次查询时,如果 没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,而不会再次发送 SQL 到数 据库。
测试:
@Test
public void ca(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession = factory.openSession();
EmpDao empDao1 = sqlSession.getMapper(EmpDao.class);
List<Emp> emps = empDao1.selectByDid(1);
System.out.println(emps);
// 过了一段时间
System.out.println("--------------------------------------------");
EmpDao empDao2 = sqlSession.getMapper(EmpDao.class);
List<Emp> emps1 = empDao2.selectByDid(1);
System.out.println(emps1);
sqlSession.close();
}
3. 二级缓存
- 二级缓存被称为sessionFactory级别的缓存,mybatis默认不支持,需要手动配置
- 二级缓存的范围更大,多个SqlSession可以共享一个Mapper的二级缓存区域。
启动二级缓存步骤:
1,实体类序列化
public class Emp implements Serializable {
private long eid;
private String ename;
private String esex;
private long eage;
private long did;
// 一对一 查询员工信息的同时把所属的部门信息查到
private Dept dept;
}
2,再核心配置文件中启动二级缓存
<setting name="cacheEnabled" value="true"/>
3,再映射文件中启动缓存
<!-- 配置当前mapper文件中所有查询语句都放入二级缓存中 -->
<cache/>
<select id="selectByDid" parameterType="long" resultType="emp" >
select * from emp where did=#{did}
</select>
在statement中设置 useCache="false" 可以禁用当前select语句的二级缓存,即每次查询都会发 出sql去查询。默认情况是true,即该sql使用二级缓存。
4,测试
@Test
public void ca2(){
SqlSessionFactory factory = MyFactoryUtil.getInstance();
SqlSession sqlSession1 = factory.openSession();
EmpDao empDao1 = sqlSession1.getMapper(EmpDao.class);
List<Emp> emps = empDao1.selectByDid(1);
System.out.println(emps);
sqlSession1.close();
// 过了一段时间
System.out.println("--------------------------------------------");
SqlSession sqlSession2 = factory.openSession();
EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class);
List<Emp> emps1 = empDao2.selectByDid(1);
System.out.println(emps1);
sqlSession2.close();
}
十三,Generator代码生成
虽然MyBatis是一个简单易学的框架,但是配置XML文件也是一件相当繁琐的一个过程,而且会出现很 多不容易定位的错误。当在工作中需要生成大量对象的时候,有太多的重复劳动,简直是生无可恋。
所以,官方开发了MyBatis Generator。它只需要很少量的简单配置,就可以完成大量的表到Java对象 的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。
1. 生成文件介绍
MyBatis Generator生成的文件包含三类:
-
- Model实体文件,一个数据库表对应生成一个 Model 实体
-
- Mapper接口文件,数据数操作方法都在此接口中定义
-
- Mapper XML配置文件
2. maven依赖
<dependencies>
<!--引入mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mybatis代码生成器-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
3. 生成配置文件
我们只需引入log4j.properties即可,无需引入mybatis-config.xml。
注意: 如果我们创建的项目是model则需要再配置中加上model的名称
generator.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm"
userId="root"
password="123456">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC
DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 生成po模型 -->
<javaModelGenerator targetPackage="com.newcapec.entity"
targetProject=".\src\main\java">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成
的类放在这个package下 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 生成mapper.xml -->
<sqlMapGenerator targetPackage="mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成dao接口 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.newcapec.dao2"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!--逆向工程不生成example类-->
<!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
<table tableName="users" domainObjectName="Users"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false">
<!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰
命名方法 -->
<property name="useActualColumnNames" value="false"/>
</table>
</context>
</generatorConfiguration>
Mybatis Generator最完整配置详解:https://blog.csdn.net/qq\_33326449/article/details/10593 0655
4. 生成文件代码
public class Generator {
public static void main(String[] args) throws Exception {
//是否覆盖已有文件
boolean overwirte = true;
DefaultShellCallback callback = new DefaultShellCallback(overwirte);
List<String> 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("代码生成成功...");
}
}
十四,第三方插件
1 安装插件
File -> Settings -> Plugins -> 搜索框输入
注意安装完成之后,最好重启IDEA
2 逆向生成mapper类
通过这个插件不用使用官方的mybatis逆向生成包,写配置文件等等,仅需连接对应数据库就可以实现 逆向生成对应的类、mapper文件等。
第一步<连接数据库>连接数据库>
第二步<配置driver>配置driver>(首次使用)
点击Driver
第三步<找到需要逆向生成的表右键选择mybatis-generator>找到需要逆向生成的表右键选择mybatis-generator>
第四步<配置>配置>
3 跳转功能
在使用mybatis框架的时候,你还在一个类一个类的点开寻找对应mapper或者dao程序的位置吗?那样 就会显得特别麻烦且浪费时间。而这个Free Mybatis Tool插件提供了跳转的功能。通过点击箭头就可以 跳转到相应的地方。