一、框架概述
1.1 软件开发常用结构
1.1.1、三层架构
三层架构包含的三层:
- 界面层(User Interface layer)
- 业务逻辑层(Business Logic Layer)
- 数据访问层(Data access layer)
三层架构分别的职责是:
界面层(表示层,视图层):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和 用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
-
数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交 给业务层,同时将业务层处理的数据保存到数据库。
他们处理请求的交互过程是:用户——> 界面层——>业务逻辑层——>数据访问层——>DB 数据库
1.1.2、为什么要使用三层架构
- 结构清晰,耦合度低,各层分工明确。
- 可维护性高,可拓展性高。
- 有利于标准化。
- 开发人员可以只关注整个结构中的其中某一层的功能实现。
- 有利于各层逻辑的复用。
1.2、框架初探究
1.2.1、什么是框架
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种认为,框架是可被应用开发者定制的应用骨架、模板。 简单的说,框架其实是半成品软件,就是一组组件,供你使用完成你自己的系统。从另一个角度来说框架一个舞台,你在舞台上做表演。在框架基础上加入你要完成的功能。 框架安全的,可复用的,不断升级的软件。
1.2.1、框架解决的问题
框架要解决的最重要的一个问题是技术整合,在 J2EE 的 框架中,有着各种各样的技术,不同的应用,系统使用不同的技术解决问题。需要从 J2EE 中选择不同的技术,而技术自身的复杂性,有导致更大的风险。
企业在开发软件项目时,主要目的是解决业务问题。 即要求企业负责技术本身,又要求解决业务问题。这是大多数企业不能完成的。框架把相关的技术融合在一起,企业开发可以集中在业务领域方面。
1.3、常用框架
1.3.1、Mybatis
MyBatis 是一个优秀的基于 java 的持久层框架,内部封装了 jdbc,开发者只需要关注 sql 语句本身,而不需要处理加载驱动、创建连接、创建 statement、关闭连接,资源等繁杂的过程。
MyBatis 通过 xml 或注解两种方式将要执行的各种 sql 语句配置起来,并通过 java 对象和 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java对象并返回。
1.3.2、Spring
Spring 框架为了解决软件开发的复杂性而创建的。Spring 使用的是基本的 JavaBean 来完成以前非常复杂的企业级开发。Spring 解决了业务对象,功能模块之间的耦合,不仅在 javase,web 中使用,大部分 Java 应用都可以从 Spring 中受益。
Spring 是一个轻量级控制反转(IoC)和面向切面(AOP)的容器。
1.3.3、SpringMVC
Spring MVC 属于 SpringFrameWork 3.0 版本加入的一个模块,为 Spring 框架提供了构建 Web应用程序的能力。现在可以 Spring 框架提供的 SpringMVC 模块实现 web 应用开发,在 web 项目中可以无缝使用 Spring 和 Spring MVC 框架
二、Mybatis简介
2.1、传统的JDBC
2.1.1、代码
public void findStudent() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//注册 mysql 驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据的基本信息 url ,username,password
String url = "jdbc:mysql://localhost:3306/springdb";
String username = "root";
String password = "123456";
//创建连接对象
conn = DriverManager.getConnection(url, username, password);
//保存查询结果
List<Student> stuList = new ArrayList<>();
//创建 Statement, 用来执行 sql 语句
stmt = conn.createStatement();
//执行查询,创建记录集,
rs = stmt.executeQuery("select * from student");
while (rs.next()) {
Student stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
//从数据库取出数据转为 Student 对象,封装到 List 集合
stuList.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭资源
if (rs != null) ;
{
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.1.2、存在的问题
我们在使用JDBC进行实际开发过程中存在的问题也是很明显的:
- 代码比较多,开发效率低。
- 需要关注 Connection ,Statement, ResultSet 对象创建和销毁。
- 对 ResultSet 查询的结果,需要自己封装为 List。
- 重复的代码比较多。
- 业务代码和数据库的操作混在一起,不利于现代的开发习惯。
2.2、MyBatis 历史
MyBatis 是 Apache 的一个开源项目 iBatis, 2010 年 6 月这个项目由 Apache Software Foundation 迁移到了 Google Code,随着开发团队转投 Google Code 旗下, iBatis3.x 正式更名为 MyBatis ,代码于 2013 年 11 月迁移到 Github。
iBatis 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。 iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO)。
2.3、MyBatis 简介
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis 可以使用简单的XML 或注解用于配置和原始映射,将接口和JavaOld Java Objects,普通的 Java 。对象)映射成数据库中的记录。
2.4、现有持久化技术的对比
2.4.1、JDBC
- SQL 夹在 Java 代码块里,耦合度高导致硬编码内伤。
- 维护不易且实际开发需求中 sql 是有变化,频繁修改的情况多见。
2.4.2、Hibernate 和 JPA。
- 长难复杂 SQL,对于 Hibernate 而言处理也不容易。
- 内部自动生产的 SQL,不容易做特殊优化。
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。导致数据库性能下降。
2.4.3、MyBatis
- 对开发人员而言,核心 sql 还是需要自己优化。
- sql 和 java 编码分开,功能边界清晰,一个专注业务、一个专注数据。
2.5、MyBatis解决的问题
减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement 。
不用编写关闭资源代码。
直接使用 java 对象,表示结果数据。让开发者专注 SQL 的处理。 其他分心的工作由 MyBatis 代劳。
2.6、下载 MyBatis
下载网址:github.com/mybatis/myb…
三、入门MyBatis
3.1、开发环境的准备
3.1.1、导入 jar包
一般的我们要导入三个jar包。
myBatis-3.4.1.jar
mysql-connector-java-5.1.37-bin.jar
log4j.jar
复制代码
3.1.2、导入 log4j 的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
3.1.3、创建测试表
-- 创建库
CREATE DATABASE test_mybatis;
-- 使用库
USE test_mybatis;
-- 创建表
CREATE TABLE user(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
pwd VARCHAR(50),
);
3.1.4、创建 javaBean
package com.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Xiao_Lin
* @date 2021/1/5 11:33
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String pwd;
}
3.1.5、创建 MyBatis 的全局配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置 mybatis 环境-->
<environments default="mysql">
<!--id:数据源的名称-->
<environment id="mysql">
<!--配置事务类型:使用 JDBC 事务(使用 Connection 的提交和回滚)-->
<transactionManager type="JDBC"/>
<!--数据源 dataSource:创建数据库 Connection 对象
type: POOLED 使用数据库的连接池
-->
<dataSource type="POOLED">
<!--连接数据库的四个要素-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1101121833"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--告诉 mybatis 要执行的 sql 语句的位置,写的路径是字节码输出路径-->
<mapper resource="com/dao/StudentDao.xml"/>
</mappers>
</configuration>
复制代码
3.1.6、编写UserDao接口
public interface UserDao {
/*查询所有数据*/
List<User> selectUser();
}
3.1.7、创建 Mybatis 的 sql 映射文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--
用来存储SQL语句的
namespace属性里面必须写 用来书写当前的mapper是对哪个DAO接口的实现
他的意思是表示该配置文件的唯一标识,意味着不同的XxxMapper.xml文件的namespace的值时不同的
-->
<!--
select标签表示查询操作,
id属性表示在mapper配置文件中是唯一标识,一般使用方法名作为其值。
paraType属性表示传入参数的类型,可省略不写,底层使用了反射,根据传入参数得到对象的类型 标签体中编写sql语句,#{变量},#表示占位符,和jdbc的?一样。
如果传入的参数是简单类型(包括String),那么该类型可以任意写
如果传入的参数是对象类型,那么变量的名称必须使用对象对应的类中的属性
resultType: 查询语句的返回结果数据类型,使用全限定类名
-->
<mapper namespace="com.mapper.UserDao">
<select id="selectAll" resultType="com.domain.User">
select * from user
</select>
</mapper>
3.1.8、配日志
mybatis.xml
文件加入日志配置,可以在控制台输出执行的 sql 语句和参数。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
复制代码
3.1.9、测试
public class test {
/*
* mybatis 入门
*/
@Test
public void testStart() throws IOException {
//1.mybatis 主配置文件
String config = "mybatis-config.xml";
//2.读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建 SqlSessionFactory 对象,目的是获取 SqlSession
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//4.获取 SqlSession,SqlSession 能执行 sql 语句
SqlSession session = factory.openSession();
//5.执行 SqlSession 的 selectList()
List<User> users = session.selectList("com.dao.UserDao.selectAll");
//6.循环输出查询结果
studentList.forEach( u -> System.out.println(u));
//7.关闭 SqlSession,释放资源
session.close();
}
}
3.2、CRUD操作
3.2.1、insert
接口中新增方法
int insertUser(User user);
复制代码
xml中新增sql语句
<insert id="insertUser">
insert into user(id,username,pwd) values(#{id},#{username},#{pwd})
</insert>
新增测试方法
@Test
public void testInsert() throws IOException {
//1.mybatis 主配置文件
String config = "mybatis-config.xml";
//2.读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//4.获取 SqlSession
SqlSession session = factory.openSession();
//5.创建保存数据的对象
User user = new User();
user.setId(1005);
user.setUsername("张三");
user.setPwd("123456");
//6.执行插入 insert
int rows = session.insert("com.dao.UserDao.insertUser",student);
//7.提交事务
session.commit();
System.out.println("增加记录的行数:"+rows);
//8.关闭 SqlSession
session.close();
}
3.2.2、update
UserDao 接口中增加方法
int updateUser(User user);
复制代码
UserDao.xml 增加 sql 语句
<update id="updateUser">
update user set username = #{username} where id = #{id}
</update>
新增测试方法
@Test
public void testUpdate() throws IOException {
//1.mybatis 主配置文件
String config = "mybatis-config.xml";
//2.读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//4.获取 SqlSession
SqlSession session = factory.openSession();
//5.创建保存数据的对象
User user = new User();
user.setId(1005);//要修改的 id
user.setUsername("李四"); //要修改的年龄值
//6.执行更新 update
int rows = session.update("com.dao.UserDao.updateUser",user);
//7.提交事务
session.commit();
System.out.println("修改记录的行数:"+rows);
//8.关闭 SqlSession
session.close();
}
3.2.3、delete
UsertDao 接口中增加方法
int deleteUser(int id);
复制代码
UserDao.xml 增加 sql 语句
<delete id="deleteUser">
delete from user where id=#{id}
</delete>
增加测试方法
@Test
public void testUpdate() throws IOException {
//1.mybatis 主配置文件
String config = "mybatis-config.xml";
//2.读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//3.创建 SqlSessionFactory 对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//4.获取 SqlSession
SqlSession session = factory.openSession();
//5.删除的 id
int id = 1001;
//6.执行删除 delete
int rows = session.delete("com.dao.UserDao.deleteStudent",id);
//7.提交事务
session.commit();
System.out.println("修改记录的行数:"+rows);
//8.关闭 SqlSession
session.close();
}
3.2.2、完成两个绑定
- Mapper 接口与 Mapper 映射文件的绑定在 Mppper 映射文件中的标签中的 namespace 中必须指定 Mapper 接口的全类名,包名加类名。
- Mapper 映射文件中的增删改查标签的 id 必须指定成 Mapper 接口中的方法,必须相同否则无法通过代理实现绑定。
3.3、Mybatis对象分析
3.3.1、Resources类
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返
回不同类型的 IO 流对象。
3.3.2、SqlSessionFactoryBuilder 类
SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。 由 于 SqlSessionFactoryBuilder 对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。所以,一般会将 该 SqlSessionFactoryBuilder 对象创建为一个方法内的局部对象,方法结束,对象销毁。
3.3.3、SqlSessionFactory类
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用
只需要一个该对象即可。创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。他有几个重载方法:
- openSession(true):创建一个有自动提交功能的 SqlSession
- openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
- openSession():同 openSession(false)
3.3.4、SqlSession 接口
SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以
SqlSession 对象的创建开始,以 SqlSession 对象的关闭结束。
SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将
其关闭。再次需要会话,再次创建。 SqlSession 在方法内部创建,使用完毕后关闭。
3.4、抽取工具类
public class MyBatisUtil {
//定义 SqlSessionFactory
private static SqlSessionFactory factory = null;
static {
//使用 静态块 创建一次 SqlSessionFactory
try{
String config = "mybatis-config.xml";
//读取配置文件
InputStream in = Resources.getResourceAsStream(config);
//创建 SqlSessionFactory 对象
factory = new SqlSessionFactoryBuilder().build(in);
}catch (Exception e){
factory = null;
e.printStackTrace();
}
}
/* 获取 SqlSession 对象 */
public static SqlSession getSqlSession(){
SqlSession session = null;
if( factory != null){
session = factory.openSession();
}
return session;
}
}
四、MyBatis 全局配置文件
4.1、MyBatis 全局配置文件简介
The MyBatis configuration contains settings and properties that have a dramatic effecton how MyBatis behaves.
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings )和属性(properties)信息。
4.2、文件结构
configuration 配置
properties 属性
settings 设置
typeAliases 类型命名
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
environments 环境
environment 环境变量
transactionManager 事务管理器
dataSource 数据源
databaseIdProvider 数据库厂商标识
mappers 映射器
4.2.1、properties 属性
properties属性是配置数据源相关属性,可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来配置
<properties>
<!--驱动名(MySQL5 和MySQL8 不同)-->
<property name="driver" value="com.mysql.jdbc.Driver" />
<!--url名字(MySQL5 和MySQL8 不同)-->
<property name="url"
<value="jdbc:mysql://localhost:3306/javaweb" />
<property name="username" value="root" />
<property name="password" value="123456" />
</properties>
然而 properties 的作用并不单单是这样,你可以创建一个资源文件,名为db.properties 的文件,将四个连接字符串的数据在资源文件中通过键值对(key=value)的方式放置,不要任何符号,一条占一行
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/javaweb
jdbc.username=root
jdbc.password=123456
复制代码
引入方式是这样
<!--
properties: 引入外部的属性文件
resource: 从类路径下引入属性文件
url: 引入网络路径或者是磁盘路径下的属性文件
-->
<properties resource="db.properties" ></properties>
在 environment 元素的 dataSource 元素中为其动态设置
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
3.2.2、settings 属性
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。一般设置数据库的懒加载和缓存之类是否开启
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
3.2.3、typeAliases 别名
类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类,引用的时候不需要再写全路径名字
<typeAliases>
<typeAlias type="com.domain.User" alias="user"/>
</typeAliases>
更简单的写法:类很多的情况下,可以批量设置别名这个包下的每一个类创建一个默认的别名,就是简 单类名小写
<typeAliases>
<package name="com.domain.User"/>
</typeAliases>
MyBatis 默认已经取好的别名,不需要我们人为去配置
3.2.4、typeHandlers 类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型,MyBatis 中默认提供的类型处理器
注意
日期和时间的处理,JDK1.8 以前一直是个头疼的问题。我们通常使用 JSR310 规范领导者 Stephen Colebourne 创建的 Joda-Time 来操作。1.8 已经实现全部的 JSR310 规范了
日期时间处理上,我们可以使用 MyBatis 基于 JSR310(Date and Time API)编写的各种日期时间类型处理器。
MyBatis3.4 以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的,如需注册,需要下载 mybatistypehandlers-jsr310,并通过如下方式注册
3.2.5、plugins 插件机制
插件是 MyBatis 提供的一个非常强大的机制,我们可以通过插件来修改 MyBatis 的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行,四大对象如下:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
3.2.6、 environments 环境配置
MyBatis 可以配置多种环境,比如开发、测试和生产环境需要有不同的配置, 每种环境使用一个 environment 标签进行配置并指定唯一标识符,可以通过environments 标签中的default 属性指定一个环境的标识符来快速的切换环境
- environment:指定具体环境
- id:指定当前环境的唯一标识
- transactionManager、dataSource 都必须有
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
<environment id="oracle">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.username}" />
<property name="password" value="${orcl.password}" />
</dataSource>
</environment>
</environments>
复制代码
3.2.6.1、transactionManager
他有三种可选的类型:JDBC ,MANAGED ,自定义
JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围,JdbcTransactionFactory。
MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期,比如JEE应用服务器上下文,ManagedTransactionFactory。
自定义:实现 TransactionFactory 接口,type=全类名/别名。
3.2.6.2、dataSource
他有四种可选的类型: UNPOOLED , POOLED . JNDI , 自定义
UNPOOLED:不使用连接池, UnpooledDataSourceFactory。
POOLED:使用连接池, PooledDataSourceFactory。
JNDI: 在 EJB 或应用服务器这类容器中查找指定的数据源。
自定义:实现 DataSourceFactory 接口,定义数据源的获取方式。
3.2.6.3、总结
际开发中我们使用 Spring 管理数据源,并进行事务控制的配置来覆盖上述配置
3.2.7、databaseIdProvider 数据库厂商标识
MyBatis 可以根据不同的数据库厂商执行不同的语句
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
Type: DB_VENDOR
使用 MyBatis 提供的 VendorDatabaseIdProvider 解析数据库厂商标识。也可以实现 DatabaseIdProvider 接口来自定义.会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短.
Property-name:数据库厂商标识
Property-value:为标识起一个别名,方便 SQL 语句使用 databaseId 属性引用
配置了 databaseIdProvider 后,在 SQL 映射文件中的增删改查标签中使用 databaseId来指定数据库标识的别名
<select id="getEmployeeById" resultType="com.atguigu.mybatis.beans.Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
</select>
MyBatis 匹配规则如下:
如果没有配置 databaseIdProvider 标签,那么 databaseId=null
如果配置了 databaseIdProvider 标签,使用标签配置的 name 去匹配数据库信息,匹配上设置 databaseId=配置指定的值,否则依旧为 null
如果 databaseId 不为 null,他只会找到配置 databaseId 的 sql 语句
MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的 所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者 会被舍弃。
3.2.8、mappers 映射器
用来在 mybatis 初始化的时候,告诉 mybatis 需要引入哪些 Mapper 映射文件
3.2.8.1、mapper 逐个注册 SQL 映射文件
resource : 引入类路径下的文件
url : 引入网络路径或者是磁盘路径下的文件
-
class : 引入 Mapper 接口.
- 有 SQL 映射文件 , 要求 Mapper 接口与 SQL 映射文件同名同位置
- 没有 SQL 映射文件 , 使用注解在接口的方法上写 SQL 语句.
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper class="com.dao.EmployeeMapper"/>
<package name="com.dao.mybatis.dao"/>
</mappers>
3.2.8.2、使用批量注册
这种方式要求 SQL 映射文件名必须和接口名相同并且在同一目录下
<mappers>
<package name="com.dao"/>
</mappers>
复制代码
五、MyBatis 映射文件
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已废弃!
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语
5.1 、Mybatis两种开发方式的比较
5.1.1、传统dao开发的弊端
我们在前面入门的例子就是传统的dao操作,我们可以看到这个操作极为繁琐,不仅繁琐,而且dao实现类也没有干什么实质性的工作,它仅仅就是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
5.1.2、现代dao开发好处
MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对 DB 进行操作。这种对 Dao 的实现方式称为 Mapper 的动态代理方式。 Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代 理实现的。
5.2、Mybatis的动态代理
5.2.1、获取代理对象
我们只需调用 SqlSession 的 getMapper()
方法,即可获取指定接口的实现类对象。该方法的参数为指定 Dao接口类的 class 值。
SqlSession session = factory.openSession();
UserDao dao = session.getMapper(UserDao.class);
//或者可以使用工具类
UserDao userDao =
MyBatisUtil.getSqlSession().getMapper(UserDao.class);
5.2.2、使用代理对象执行sql语句
@Test
public void TestUpdate(){
User user = new User(16,"赵六","110");
userDao.update(user);
}
复制代码
5.2.3、动态代理原理
这种手段我们称为动态代理,我们debug一下可以看到
点进MapperProxy 类定义:
invoke()方法:
点进去execute方法,重点方法:
5.3、主键生成方式和获取主键值
5.3.1、主键生成方式
- 支持主键自增,例如 MySQL 数据库
- 不支持主键自增,例如 Oracle 数据库
5.3.2、 获取主键值
若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server ),则可以设置`useGeneratedKeys=”true”`,然后再把 keyProperty 设置到目标属性上。
<insert id="insertEmployee"
insert id="insertEmployee"parameterType="com.atguigu.mybatis.beans.Employee" databaseId="mysql" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender})
</insert>
而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用
<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee" databaseId="oracle">
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select employee_seq.nextval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})
</insert>
或者这样写
<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee" databaseId="oracle">
<selectKey order="AFTER" keyProperty="id" resultType="integer">
select employee_seq.currval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(employee_seq.nextval,#{lastName},#{email},#{gender})
</insert>
5.4、参数传递
5.4.1、参数传递的方式
5.4.1.1、单个简单参数
可以接受基本类型,对象类型。这种情况 MyBatis 可直接使用这个参数,不需要经过任何处理。
在mapper.xml中用占位符 #{ 任意字符 }
来表示这个参数,和方法的参数名无关。但是一般我们都会用方法的参数名来命名。
//接口中的方法:
User selectById(int id);
<!--mapper文件-->
<select id="selectById" resultType="com.bjpowernode.domain.Student">
select id,username,pwd from user where id=#{abcd}
</select>
<!--
#{abcd} , abcd是自定义的变量名称,可以不和方法参数名相同,但是实际开发中一般是相同的。
-->
5.4.1.2、多个参数(贴注解)
任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,或者 0,1…,值就是参数的值。
我们一般需要在方法形参前面加入@Param(“自定义参数名”), mapper 文件使用#{自定义参数名}来表示传入多个参数。
//接口方法
List<User> selectUserByCondition(@Param("username") String username, @Param("pwd") int pwd);
<!--mapper文件-->
<select id="selectUserByCondition" resultType="com.domain.User">
select id,username,pwd from student where username = #{username} or pwd = #{pwd}
</select>
5.4.1.3、多个参数(封装成对象)
当我们需要传递多个参数的时候,我们可以将这些对象直接封装成一个对象,我们就直接传入JavaBean对象即可,在占位符内写对象的属性。
5.4.1.4、Map
Map 集合可以存储多个值,使用 Map 向 mapper 文件一次传入多个参数。Map 集合使用 String 的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值。
//接口方法
List<User> selectMultiMap(Map<String,Object> map);
<!--mapper文件-->
<select id="selectMultiMap" resultType="com.domain.User">
select id,username,pwd from user where username=#{username} or pwd =#{pwd}
</select>
5.4.1.5、Collection/Array
会被MyBatis 封装成一个map 传入, Collection 对应的key 是collection,Array 对应的key是 array. 如果确定是 List 集合,key 还可以是 list.
5.4.2、参数传递的源码分析
以命名参数为例:
public User getUserByIdAndUsername
(@Param("id")Integer id, @Param("username")String username);
源码分析
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX +
String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
5.4.3、参数处理
参数位置支持的属性
javaType、jdbcType、mode、numericScale、resultMap、typeHandler、jdbcTypeName、expression
使用实例
**实际上通常被设置的是:为可能为空的列名指定 jdbcType **
<select id="selectMultiObject" resultType="com.domain.User">
select id,username pwd from user
where username=#{username,javaType=string,jdbcType=VARCHAR}
or pwd =#{pwd,javaType=int,jdbcType=INTEGER}
</select>
5.4.4、参数的获取方式
5.4.4.1、#
#
占位符:告诉 mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}
代替sql 语句的?
。这样做更安全,更迅速,通常也是首选做法.
<!--mapper文件-->
<select id="selectById" resultType="com.domain.User">
select id,username,pwd from user where id=#{id}
</select>
//转为 MyBatis 的执行是:
String sql=” select id,username,pwd from user where id=?”;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,1005);
//解释:
where id=就是 where id=#{id}
ps.setInt(1,1005) , 1005 会替换掉 #{id}
5.4.4.2、$
$
字符串替换:告诉 mybatis 使用<math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>所包含的“字符串”替换所在位置。使用</mtext><mo>?</mo><mo>?</mo><mi>S</mi><mi>t</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi>m</mi><mi>e</mi><mi>n</mi><mi>t</mi><mo>?</mo><mo>?</mo><mtext>把</mtext><mi>s</mi><mi>q</mi><mi>l</mi><mtext>语句和</mtext></mrow><annotation encoding="application/x-tex">所包含的“字符串”替换所在位置。使用 Statement 把 sql 语句和</annotation></semantics></math>所包含的“字符串”替换所在位置。使用??Statement??把sql语句和{}的内容连接起来。主要用在替换表名,列名,不同列排序等操作。
//需求:使用不同列作为查询条件
//接口方法
User findByDiffField(@Param("col") String colunName,@Param("cval") Object value);
<!--mapper文件-->
<select id="findByDiffField" resultType="com.domain.User">
select * from user where ${col} = #{cval}
</select>
5.5、select 查询的几种情况
5.5.1、查询单行数据返回单个对象
public User getUserById(Integer id );
5.5.2、查询多行数据返回对象的集合
public List<User> getAllUser();
5.5.3、查询单行数据返回 Map 集合
public Map<String,Object> getUserByIdReturnMap(Integer id );
5.5.4、 查询多行数据返回 Map 集合
@MapKey("id") // 指定使用对象的哪个属性来充当 map 的 key
public Map<Integer,User> getAllUserReturnMap();
5.6、resultType 自动映射
执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
接口方法返回是集合类型,需要指定集合中的类型,不是集合本身。
5.6.1、简单类型
//接口方法
int countUser();
<!--mapper 文件-->
<select id="countUser" resultType="int">
select count(*) from user
</select>
5.6.2、对象类型
//接口方法
List<User> selectUsers();
<!--mapper文件-->
<select id="selectUsers" resultType="com.domain.User">
select id,username,pwd from user
</select>
5.6.3、resultType的原理
使用构造方法创建对象。调用 setXXX 给属性赋值。
sql 语句列 | java 对象方法 |
---|---|
id | setId(rs.setInt("id")) |
username | setUsername(rs.setString("username")) |
pwd | setPwd(rs.setString("pwd")) |
- autoMappingBehavior 默认是 PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean 属性名一致
- 如果 autoMappingBehavior 设置为 null 则会取消自动映射
- 数据库字段命名规范,POJO 属性符合驼峰命名法,如 A_COLUMN aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
5.7、resultMap 自定义映射
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。 常用在列名和 java 对象属性名不一样的情况。
- id :用于完成主键值的映射。
- result :用于完成普通列的映射。
- association :一个复杂的类型关联,许多结果将包成这种类型。
- collection : 复杂类型的集。
属性 | 含义 |
---|---|
property | 映射到列结果的字段或属性,例如:"username"或"address.stree.number" |
column | 数据表的列名,通常和resultSet.getString(columnName) 的返回值一致 |
<!--resultMap: resultMap 标签中的 id 属性值-->
<select id="getEmployeeById" resultMap="myEmp">
select id, last_name,email, gender from tbl_employee where id =#{id}
</select>
<!-- 创建 resultMap
id:自定义的唯一名称,在<select>使用
type:期望转为的 java 对象的全限定名称或别名
-->
<resultMap type="com.domain.Employee" id="myEmp">
<!-- 主键字段使用 id -->
<id column="id" property="id" />
<!--非主键字段使用 result-->
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
5.7.1、association
5.7.1.1、association
POJO 中的属性可能会是一个对象,我们可以使用联合查询,并以级 联属性的方式封装对象.使用 association 标签定义对象的封装规则
@Data
public class Department {
private Integer id ;
private String departmentName ;
}
@Data
public class Employee {
private Integer id ;
private String lastName;
private String email ;
private String gender ;
private Department dept ;
}
<select id="getEmployeeAndDept" resultMap="myEmpAndDept" >
SELECT e.id eid, e.last_name, e.email,e.gender ,d.id did, d.dept_name
FROM tbl_employee e , tbl_dept d
WHERE e.d_id = d.id
AND e.id = #{id}
</select>
<resultMap type="com.domain.Employee" id="myEmpAndDept">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept" javaType="com.domain.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
5.7.1.2、association 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过员工的 id 查询员工信息
- 再通过查询出来的员工信息中的外键(部门 id)查询对应的部门信息.
<select id="getEmployeeAndDeptStep" resultMap="myEmpAndDeptStep">
select id, last_name, email,gender,d_id from tbl_employee where id =#{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Employee" id="myEmpAndDeptStep">
<id column="id" property="id" />
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id"
fetchType="eager">
</association>
</resultMap>
5.7.1.3、association 分步查询使用延迟加载
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的Settings 中进行如下的配置
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
复制代码
5.7.2、collection
5.7.2.1、collection
POJO 中的属性可能会是一个集合对象,我们可以使用联合查询,并以级联属性的方式封 装对象.使用 collection 标签定义对象的封装规则
@Data
public class Department {
private Integer id ;
private String departmentName ;
private List<Employee> emps ;
}
<select id="getDeptAndEmpsById" resultMap="myDeptAndEmps">
SELECT d.id did, d.dept_name ,e.id eid ,e.last_name ,e.email,e.gender
FROM tbl_dept d
LEFT OUTER JOIN tbl_employee e
ON d.id = e.d_id
WHERE d.id = #{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Department" id="myDeptAndEmps">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
property: 关联的属性名
ofType: 集合中元素的类型
-->
<collection property="emps" ofType="com.atguigu.mybatis.beans.Employee">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
5.7.2.2、collection 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过部门的 id 查询部门信息
- 再通过部门 id 作为员工的外键查询对应的部门信息.
<select id="getDeptAndEmpsByIdStep" resultMap="myDeptAndEmpsStep">
select id ,dept_name from tbl_dept where id = #{id}
</select>
<resultMap type="com.atguigu.mybatis.beans.Department" id="myDeptAndEmpsStep">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps" select="com.atguigu.mybatis.dao.EmployeeMapper.getEmpsByDid" column="id"></collection>
</resultMap>
5.7.3、 扩展: 分步查询多列值的传递
- 如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成Map 来进行传递,语法如下: {k1=v1, k2=v2....}。
- 在所调用的查询方,取值时就要参考 Map 的取值方式,需要严格的按照封装 map时所用的 key 来取值。
5.7.4、扩展: association 或 collection 的 fetchType 属性
- 在
<association>
和<collection>
标签中都可以设置 fetchType,指定本次查询是否要使用延迟加载。默认为 fetchType=”lazy” ,如果本次的查询不想使用延迟加载,则可设置为fetchType=”eager”。 - fetchType 可以灵活的设置查询是否需要使用延迟加载,而不需要因为某个查询不想使用延迟加载将全局的延迟加载设置关闭。
5.8、模糊查询like
模糊查询的实现有两种方式,:
- 在 java 代码中给查询数据加上
%
。 - mapper 文件中使用
like name "%" #{xxx} "%"
5.8.1、方式一
来查询姓名有“力”的
//接口方法
List<User> selectLikeFirst(String username);
<!--接口文件-->
<select id="selectLikeFirst" resultType="com.domain.User">
select id,username,pwd from user where username like #{username}
</select>
复制代码
5.8.2、方式二
//接口方法
List<User> selectLikeSecond(String username);
<!--mapper文件-->
<select id="selectLikeSecond" resultType="com.domain.User">
select id,username,pwd from user where username like "%" #{studentName} "%"
</select>
六、MyBatis 动态 SQL
6.1、MyBatis 动态 SQL 简介
动态 SQL,通过 MyBatis 提供的各种标签对条件作出判断以实现动态拼接 SQL 语句。这里的条件判
断使用的表达式为 OGNL 表达式。常用的动态 SQL 标签有、、、等。
MyBatis 的动态 SQL 语句,与 JSTL 中的语句非常相似。动态 SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的 SQL 语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的 SQL 语句。此时,可使用动态 SQL 来解决这样的问题。
- 动态 SQL 是 MyBatis 强大特性之一。极大的简化我们拼装 SQL 的操作
- 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似
- MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作
- OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的 EL,SpEL 等
注意:xml 中特殊符号如”,>,<等这些都需要使用转义字符
5.2、if和 where
if
If 用于完成简单的判断.
Where
Where 用于解决 SQL 语句中 where 关键字以及条件中第一个 and 或者 or 的问题
<select id="getEmpsByConditionIf" resultType="com.atguigu.mybatis.beans.Employee">
select id , last_name ,email , gender
from tbl_employee
<where>
<if test="id!=null">
and id = #{id}
</if>
<if test="lastName!=null && lastName!=""">
and last_name = #{lastName}
</if>
<if test="email!=null and email.trim()!=''">
and email = #{email}
</if>
<if test=""m".equals(gender) or "f".equals(gender)">
and gender = #{gender}
</if>
</where>
</select>
5.3、trim
Trim 可以在条件判断完的 SQL 语句前后 添加或者去掉指定的字符,常用方法如下:
prefix: 添加前缀 prefixOverrides: 去掉前缀 suffix: 添加后缀 suffixOverrides: 去掉后缀
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.beans.Employee">
select id , last_name ,email , gender from tbl_employee
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id = #{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name = #{lastName} and
</if>
<if test="email!=null and email.trim()!=''">
email = #{email} and
</if>
<if test=""m".equals(gender) or "f".equals(gender)">
gender = #{gender}
</if>
</trim>
</select>
5.4、set
set 主要是用于解决修改操作中 SQL 语句中可能多出逗号的问题
<update id="updateEmpByConditionSet">
update tbl_employee
<set>
<if test="lastName!=null && lastName!=""">
last_name = #{lastName},
</if>
<if test="email!=null and email.trim()!=''">
email = #{email} ,
</if>
<if test=""m".equals(gender) or "f".equals(gender)">
gender = #{gender}
</if>
</set>
where id =#{id}
</update>
5.5、choose(when、otherwise)
choose 主要是用于分支判断,类似于 java 中的 switch case,只会满足所有分支中的一个
<select id="getEmpsByConditionChoose" resultType="com.atguigu.mybatis.beans.Employee">
select id ,last_name, email,gender from tbl_employee
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="lastName!=null">
last_name = #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<otherwise>
gender = 'm'
</otherwise>
</choose>
</where>
</select>
5.6、foreach
foreach 主要用户循环迭代
collection: 要迭代的集合 item: 当前从集合中迭代出的元素 open: 开始字符 close:结束字符 separator: 元素与元素之间的分隔符 index: 迭代的是 List 集合: index 表示的当前元素的下标 迭代的 Map 集合: index 表示的当前元素的 key
<select id="getEmpsByConditionForeach" resultType="com.atguigu.mybatis.beans.Employee">
select id , last_name, email ,gender from tbl_employee where id in
<foreach collection="ids" item="curr_id" open="(" close=")" separator="," >
#{curr_id}
</foreach>
</select>
5.6、sql
sql 标签是用于抽取可重用的 sql 片段,将相同的,使用频繁的 SQL 片段抽取出来,单 独定义,方便多次引用.
抽取sql
<sql id="selectSQL">
select id , last_name, email ,gender from tbl_employee
</sql>
引用 SQL
<include refid="selectSQL"></include>
六、MyBatis 缓存机制
6.1、缓存的简介
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率
- MyBatis 系统中默认定义了两级缓存,一级缓存和二级缓存
- 默认情况下,只有一级缓存(SqlSession 级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存
- 为了提高扩展性。MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存
6.2、 一级缓存的使用
一级缓存(local cache), 即本地缓存, 作用域默认为 sqlSession。当 Session flush 或close 后, 该 Session 中的所有 Cache 将被清空。本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
6.2.1、一级缓存的工作机制
同一次会话期间只要查询过的数据都会保存在当前 SqlSession 的一个 Map 中 key: hashCode+查询的 SqlId+编写的 sql 查询语句+参数
6.2.2、一级缓存失效的几种情况
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 但是查询条件不同
- 同一个 SqlSession 两次查询期间执行了任何一次增删改操作
- 同一个 SqlSession 两次查询期间手动清空了缓存
6.3、二级缓存的使用
二级缓存(second level cache),全局作用域缓存, 二级缓存默认不开启,需要手动配置。
MyBatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现 Serializable 接口
二级缓存在 SqlSession 关闭或提交之后才会生效
二级缓存使用的步骤:
- 全局配置文件中开启二级缓存
- 需要使用二级缓存的映射文件处使用 cache 配置缓存
- 注意:POJO 需要实现 Serializable 接口
二级缓存相关的属性
eviction=“FIFO”:缓存回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。默认的是 LRU。
flushInterval:刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size:引用数目,正整数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被 修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全, 因此默认是 false。
6.4、缓存的相关属性设置
属性 | 含义 |
---|---|
全局 setting 的 cacheEnable | 配置二级缓存的开关,一级缓存一直是打开的 |
select 标签的 useCache 属性 | 配置这个 select 是否使用二级缓存。一级缓存一直是使用的 |
sql 标签的 flushCache 属性 | 增删改默认 flushCache=true。sql 执行以后,会同时清空一级和二级缓存。查询默认 flushCache=false。 |
sqlSession.clearCache(): | 只是用来清除一级缓存 |
6.5、整合第三方缓存
为了提高扩展性。MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存,EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider
整合 EhCache 缓存的步骤
- 导入 ehcache 包,以及整合包,日志包(maven也行) ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
- 编写 ehcache.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
<!--
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache
便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始
终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁
盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过
timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置
时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活
时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认
是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是
false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120
秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时
候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU
(最不常使用)和FIFO(先进先出)
-->
- 配置 cache 标签
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
七、MyBatis 逆向工程
7.1、逆向工程简介
MyBatis Generator: 简称 MBG,是一个专门为 MyBatis 框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及 bean 类。支持基本的增删改查,以及 QBC 风格的条件查询。但是表连接、存储过程等这些复杂 sql 的定义需要我们手工编写
官方文档地址 www.mybatis.org/generator/ 官方工程地址 github.com/mybatis/gen…
7.2、逆向工程的配置
- 导入逆向工程的 jar 包:mybatis-generator-core-1.3.2.jar
- 编写 MBG 的配置文件(重要几处配置),可参考官方手册
<?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>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD
MyBatis3: 生成带条件的CRUD
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis_1129"
userId="root"
password="1234">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.mybatis.beans"
targetProject=".\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.atguigu.mybatis.dao"
targetProject=".\conf">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.atguigu.mybatis.dao" targetProject=".\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<table tableName="tbl_dept" domainObjectName="Department"></table>
<table tableName="tbl_employee" domainObjectName="Employee"></table>
</context>
</generatorConfiguration>
- 编写java代码运行
@Test
public void testMBG() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
复制代码
7.3、逆向工程的使用
基本查询的测试
@Test
public void testSelect() throws Exception {
SqlSessionFactory ssf = getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
EmployeeMapper mapper =
session.getMapper(EmployeeMapper.class);
List<Employee> emps = mapper.selectAll();
for (Employee employee : emps) {
System.out.println(employee);
}
} finally {
session.close();
}
}
带条件查询的测试
@Test
public void testSelect() throws Exception {
SqlSessionFactory ssf = getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
EmployeeMapper mapper =
session.getMapper(EmployeeMapper.class);
//条件查询: 名字中带有'张' 并且 email中'j' 或者 did = 2
EmployeeExample example = new EmployeeExample();
Criteria criteria = example.createCriteria();
criteria.andLastNameLike("%张%");
criteria.andEmailLike("%j%");
//or
Criteria criteriaOr = example.createCriteria();
criteriaOr.andDIdEqualTo(2);
example.or(criteriaOr);
List<Employee> emps = mapper.selectByExample(example);
for (Employee employee : emps) {
System.out.println(employee);
}
} finally {
session.close();
}
}
八、扩展-PageHelper 分页插件
8.1、PageHelper 分页插件简介
PageHelper 是 MyBatis 中非常方便的第三方分页插件,官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
8.2、PageHelper 的使用步骤
导入相关包 pagehelper-x.x.x.jar 和 jsqlparser-0.9.5.jar
在 MyBatis 全局配置文件中配置分页插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
使用 PageHelper 提供的方法进行分页
可以使用更强大的 PageInfo 封装返回结果
8.3、Page 对象的使用
在查询之前通过 PageHelper.startPage(页码,条数)设置分页信息,该方法返回 Page 对象
@Test
public void testPageHelper() throws Exception{
SqlSessionFactory ssf = getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
EmployeeMapper mapper =
session.getMapper(EmployeeMapper.class);
//设置分页信息
Page<Object> page = PageHelper.startPage(9, 1);
List<Employee> emps = mapper.getAllEmps();
for (Employee employee : emps) {
System.out.println(employee);
}
System.out.println("=============获取分页相关的信息
=================");
System.out.println("当前页: " + page.getPageNum());
System.out.println("总页码: " + page.getPages());
System.out.println("总条数: " + page.getTotal());
System.out.println("每页显示的条数: " + page.getPageSize());
} finally {
session.close();
}
}
8.4、PageInfo 对象的使用
在查询完数据后,使用 PageInfo 对象封装查询结果,可以获取更详细的分页信息以及可以完成分页逻辑
@Test
public void testPageHelper1() throws Exception{
SqlSessionFactory ssf = getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
EmployeeMapper mapper =
session.getMapper(EmployeeMapper.class);
//设置分页信息
Page<Object> page = PageHelper.startPage(9, 1);
List<Employee> emps = mapper.getAllEmps();
//
PageInfo<Employee> info = new PageInfo<>(emps,5);
for (Employee employee : emps) {
System.out.println(employee);
}
System.out.println("=============获取详细分页相关的信息
=================");
System.out.println("当前页: " + info.getPageNum());
System.out.println("总页码: " + info.getPages());
System.out.println("总条数: " + info.getTotal());
System.out.println("每页显示的条数: " + info.getPageSize());
System.out.println("是否是第一页: " + info.isIsFirstPage());
System.out.println("是否是最后一页: " + info.isIsLastPage());
System.out.println("是否有上一页: " + info.isHasPreviousPage());
System.out.println("是否有下一页: " + info.isHasNextPage());
System.out.println("============分页逻辑===============");
int [] nums = info.getNavigatepageNums();
for (int i : nums) {
System.out.print(i +" " );
}
} finally {
session.close();
}
}
九、SSM 框架整合
9.1、整合注意事项
- 查看不同 MyBatis 版本整合 Spring 时使用的适配包;
- 下载整合适配包:github.com/mybatis/spr…
- 官方整合示例,jpetstore:github.com/mybatis/jpe…
9.2、整合思路、步骤
搭建环境
创建一个动态的 WEB 工程,导入 SSM 需要使用的 jar 包,导入整合适配包,导入其他技术的一些支持包 连接池 数据库驱动 日志....
Spring + Springmvc
- 在web.xml 中配置: Springmvc 的前端控制器 实例化Spring 容器的监听器 ,字符编码过滤器 REST 过滤器
- 创建 Spring 的配置文件: applicationContext.xml:组件扫描、 连接池、 事务.....
- 创建 Springmvc 的配置文件: springmvc.xml : 组件扫描、 视图解析器
MyBatis
- 创建 MyBatis 的全局配置文件
- 编写实体类 Mapper 接口 Mapper 映射文件
Spring + MyBatis
- MyBatis 的 SqlSession 的创建 .
- MyBatis 的 Mapper 接口的代理实现类
测试
9.3、整合的配置
9.3.1、web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<!-- 字符编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- REST 过滤器 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 实例化SpringIOC容器的监听器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Springmvc的前端控制器 -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
9.3.2、Spring 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu.ssm">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 连接池 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
9.3.3 SpringMVC 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu.ssm" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
</beans>
9.3.4、MyBatis 配置
全局文件的配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- Spring 整合 MyBatis 后, MyBatis中配置数据源,事务等一些配置都可以
迁移到Spring的整合配置中。MyBatis配置文件中只需要配置与MyBatis相关
的即可。
-->
<!-- settings: 包含很多重要的设置项 -->
<settings>
<!-- 映射下划线到驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 设置Mybatis对null值的默认处理 -->
<setting name="jdbcTypeForNull" value="NULL"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 配置开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
SQL 映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.ssm.mapper.EmployeeMapper">
<!-- public List<Employee> getAllEmps(); -->
<select id="getAllEmps" resultMap="myEmpsAndDept" >
select e.id eid, e.last_name,e.email,e.gender, d.id did, d.dept_name
from tbl_employee e ,tbl_dept d
where e.d_id = d.id
</select>
<resultMap type="com.atguigu.ssm.beans.Employee" id="myEmpsAndDept">
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept" javaType="com.atguigu.ssm.beans.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
</mapper>
复制代码
Spring 整合 MyBatis 配置
<!-- Spring 整合 Mybatis -->
<!--1\. SqlSession -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- MyBatis的配置文件 -->
<property name="configLocation"
value="classpath:mybatis-config.xml"></property>
<!-- MyBatis的SQL映射文件 -->
<property name="mapperLocations"
value="classpath:mybatis/mapper/*.xml"></property>
<property name="typeAliasesPackage"
value="com.atguigu.ssm.beans"></property>
</bean>
<!-- Mapper接口
MapperScannerConfigurer 为指定包下的Mapper接口批量生成代理实现类.bean
的默认id是接口名首字母小写.
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.atguigu.ssm.mapper"></property>
</bean>
<!-- <mybatis-spring:scan base-package="com.atguigu.ssm.mapper"/> -->
测试