不定期补充、修正、更新;欢迎大家讨论和指正
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
如以下架构图,它显示了驱动程序管理器相对于JDBC驱动程序和Java应用程序的位置
sun公司提出JDBC接口规范,各个厂商对自己的产品实现相关符合规范的驱动,
只要符合JDBC接口规范,我们都可以通过JDBC统一的API操控不同的数据库产品。
这里只是简单回顾JDBC,如果没学过可以参考以下视频
尚硅谷JDBC核心技术视频教程
使用JDBC来访问数据库大致可以分为以下步骤
配置连接数据库的信息,获取连接和关闭资源都是重复的代码,所以这里写一个工具类来进行封装
public class JdbcUtils {
private static Connection conn;
static {
try {
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc_conn.properties");//读取JDBC配置文件,自己定义
Properties p = new Properties();
p.load(is);
String url = p.getProperty("url");//数据库URL
String user = p.getProperty("user");//用户名
String password = p.getProperty("password");//密码
String driverClass =p.getProperty("driverClass");//数据库驱动
Class.forName(driverClass);
conn = DriverManager.getConnection(url, user, password);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
throwables.printStackTrace();
}
}
public static Connection getConnection() {//获取连接
return conn;
}
public static void closeResource(Connection conn, PreparedStatement ps){//关闭资源
try {
if(ps !=null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(conn !=null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public static void closeResource(Connection conn, PreparedStatement ps, ResultSet rs){//关闭资源
try {
if(ps !=null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(conn !=null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(rs !=null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
插入
@Override
public void insert(Jobs jobs) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JdbcUtils.getConnection();
String sql = "INSERT INTO `jobs`(`job_id`,`job_title`,`min_salary`,`max_salary`)VALUE(?,?,?,?);";
ps = conn.prepareStatement(sql);//预编译
ps.setString(1,jobs.getJob_id());//填充占位符
ps.setString(2,jobs.getJob_title());
ps.setInt(3,jobs.getMin_salary());
ps.setInt(4,jobs.getMax_salary());
ps.execute();//执行
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps);//关闭连接
}
}
删除
@Override
public void deleteByID(String ID) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JdbcUtils.getConnection();
String sql = "DELETE FROM `jobs` WHERE `job_id`=?;";
ps = conn.prepareStatement(sql);
ps.setString(1,ID);
ps.execute();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps);
}
}
查询
@Override
public Jobs getByID(String ID) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;//查询就复杂些,需要接收结果集
try {
conn = JdbcUtils.getConnection();
String sql = "SELECT * FROM `jobs` WHERE `job_id` = ?;";
ps = conn.prepareStatement(sql);
ps.setString(1,ID);
rs = ps.executeQuery();
if (rs.next()) {
Jobs jobs = new Jobs();
jobs.setJob_id(rs.getString(1));
jobs.setJob_title(rs.getString(2));
jobs.setMin_salary(rs.getInt(3));
jobs.setMax_salary(rs.getInt(4));
return jobs;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.closeResource(conn,ps,rs);
}
return null;
在使用传统的JDBC中,我们可以发现诸多不便
尽管有一些简化的工具比如Dbutils和JDBCTemplate等,但能做的也很有限。
随后市面上就出现了许多持久层框架来简化开发和操作,如MyBatis、Hibernate、TopLink、Guzz、jOOQ、Spring Data。如今MyBatis占主流,同时也是主流MVC框架——SSM(Spring + SpringMVC + MyBatis)重要的一环。
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
–摘自官网
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
–摘自百度百科
MyBatis官方帮助文档
导入Mybatis
jar包:mybatis
Maven依赖
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.5version>
dependency>
mysql驱动
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.37version>
dependency>
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
根据官方所说,SqlSessionFactory是整个MyBatis的核心,SqlSessionFactory通过SqlSessionFactoryBuilder获取,而SqlSessionFactoryBuilder可以通过XML配置文件来构造,以下为官方给出的XML配置文件模板(配置文件名根据规范一般为mybatis-config.xml)。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
mappers>
configuration>
接下来就可以获取SqlSessionFactory实例了,通过SqlSessionFactory可以获取SqlSession实例,而SqlSession才是真正与数据库交互的东西。
为了后续方便使用,可以写成工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
在测试前先把主配置文件中的mapper映射注释掉(写SQL的地方),因为这是官方给的例子,我们并没有该mapper需要映射,否则会报错
成功获取SqlSession,但不代表能成功连接到数据库,就算连接数据库的账号密码是错误的,在这时也不会有任何提示
利用SqlSession向数据库做CURD操作,仍以下表为例
ORM(对象关系映射),创建实体类,用于后面接收结果集
按照原生JDBC,接下来就是写SQL语句了,MyBatis提供在XML中编写SQL语句的形式,这些文件我们称为mapper
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
select>
mapper>
命名空间是为了防止id冲突,这里简单使用完全不怕,但既然是必须的,就先创建一个接口
接下来很重要的一步,将我们的mapper在主配置文件中注册
有些人习惯把mapper放在src目录下,会很容易遇到找不到该mapper路径的异常,因为Maven一般只读取resources下的xml文件,所以解决方法就是放在resources目录下
如果偏要放在其他目录下比如这里的src,需要在pom.xml添加以下配置
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
build>
接下来就可以通过sqlSession向数据库进行CURD操作了
我们先用用selectOne()方法,该方法提供两个参数,一个是mapper接口中的方法,第二个是传入的查询参数
sqlSession的方法过于局限,同时为了避免标签冲突,更好的做法是获取mapper所绑定的接口,通过接口调用这些方法,这样不仅更安全,可读性好、传参也方便
经过简单的使用,可以发现MyBatis很好地解决了原生JDBC的弊端:SQL语句在外部文件中编写,降低耦合性、结果集的接收MyBatis自动完成,只需要指定接收结果集的类即可,使得我们可以专注于SQL语句的构造。
接下来把剩下的CRUD写完,不过在实现之前导入log4j日志功能,我们只能通过SqlSession是否操作数据库成功,但是如果出错,仅仅通过SqlSession很难进行排错。
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
导入依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.12version>
dependency>
创建log4j的配置文件(log4j.properties,最好放在resource下)
这里是从Log4j的配置与使用详解直接扒下来的
# Global logging configuration
# 设置日志输出级别以及输出目的地,可以设置多个输出目的地,开发环境下,日志级别要设置成DEBUG或者ERROR
# 前面写日志级别,逗号后面写输出目的地:我自己下面设置的目的地相对应,以逗号分开
# log4j.rootLogger = [level],appenderName1,appenderName2,…
log4j.rootLogger=DEBUG,CONSOLE,LOGFILE
#### 控制台输出 ####
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 输出到控制台
log4j.appender.CONSOLE.Target = System.out
# 指定控制台输出日志级别
log4j.appender.CONSOLE.Threshold = DEBUG
# 默认值是 true, 表示是否立即输出
log4j.appender.CONSOLE.ImmediateFlush = true
# 设置编码方式
log4j.appender.CONSOLE.Encoding = UTF-8
# 日志输出布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p (%c:%L) - %m%n
#### 输出错误信息到文件 ####
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
# 指定输出文件路径
#log4j.appender.LOGFILE.File =F://Intellij idea/logs/error.log
log4j.appender.LOGFILE.File =./logs/error.log
#日志输出到文件,默认为true
log4j.appender.LOGFILE.Append = true
# 指定输出日志级别
log4j.appender.LOGFILE.Threshold = ERROR
# 是否立即输出,默认值是 true,
log4j.appender.LOGFILE.ImmediateFlush = true
# 设置编码方式
log4j.appender.LOGFILE.Encoding = UTF-8
# 日志输出布局
log4j.appender.LOGFILE.layout = org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.LOGFILE.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
有了日志功能,可以了解到SQL完整的执行过程,方便调试
在接口制定CRUD方法
插入
对于增删改操作,SqlSession默认是不会自动提交事务的,需要手动提交
查询全部
查询多个结果,虽然返回值是集合,但在SQL语句返回值设置为集合中元素的类型就行
细心的朋友可以发现在以上CRUD传参的形式都有所不同
在MyBatis中,当传参只有一个时,在SQL语句写的参数名可以随意
比如删除操作,因为不会产生歧义,名字怎么取都没关系
当传的参数有多个时,可以传map,SQL语句的#{}中的变量名需要和map中的key相同
也可以通过Java Bean传递多个参数,SQL语句的#{}中的变量名和实体类的属性相同
还有是使用@param注解的形式,这里不进行演示
MyBatis(四)SQL语句中参数传递的五种方法
还有需要注意的是 ${},#{}的区别
在很多地方,${} 的作用是直接取值,这种方式容易产生SQL注入的危险(以预编译的方式将参数设置到sql中)
而#{} 运行结果会是一个?占位符 ,和使用PreparedStatement目的一致
当然${} 并不是一无是处,可以用于分库分表排序等原生jdbc不支持占位符的地方,例如
select * from ${month}_salary order by ${name}
全局配置文件的属性设置,完全可以看官方文档
mybatis-配置
这里学习几个常用的
属性(properties)
主要用来获取外部文件的内容,比如连接数据库的信息可以单独放到另一个文件,然后通过properties引入
也可以直接在标签内设置,但是优先级还是文件中的高
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
比如之前mapper用到类时写的是全路径比较长
取别名
当需要某个包下有很多类需要取别名时,可以以包为单位为其下的所有类取别名
类的别名为类名(无视大小写)
环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,所以要在< environments default=“development” >标签内选择具体的环境
每套环境内都要设置transactionManager和dataSource,具体内容自己看
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。自行了解,后续使用时会提到。
在前面select语句中,对于复杂的结果集我们常用JavaBean 或 POJO来接收,也就是resultType=‘JavaBean’,虽然看似是ResultType完成了结果集的映射,但实际上是ResultMap完成了这些事。MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
什么时候用ResultMap呢,当数据库的字段和javaBean属性字段不一样时就可以使用(数据库字段命名一般为job_id,而Java是驼峰式命名)
之前属性名和数据库字段是一一对应的,所以直接可以用resultType接收
假如改成驼峰式命名,我们来看看还能接收到结果集吗
可以看到能查询到结果,但是没接收到结果集
这种情况下容易解决,我们可以在全局配置文件中的setting标签内开启驼峰命名自动映射
也可以利用SQL语法中的as取别名(别忘了关掉驼峰命名自动映射,不然不知道有没有成功)
还有一种方法就是用resultMap作映射了
resultMap标签有三个属性
resultMap下有几个子标签,现在先使用这两个(id和result的作用一样)
如果数据库字段和属性名一一对应或者驼峰转换后对应,直接用resultType就行,用resultMap就多次一举了
但是对于多表查询或者一些复杂的查询就不得不用resultMap来封装结果了,在此之前先了解resultMap标签内所有的属性
在先前我们已经有了一张job表
现在根据job_id与另一张employee表进行多表查询(为了方便点,这里只考虑以下四个属性,以及job表的job_title属性)
而这两张表结合呈现一对多的关系(以job表为主表,一个job可以有多个employee),而反过来就是多对一的关系(以employee为主表),因此使用resultMap时要对两种关系分别进行实现。
构造实体类
因为job表可以查出多个employees,所以要为其添加一个列表类型的属性(这种其实方式是很不好的,当初刚学还不懂这些,因为我们要避免修改实体类,在web开发中会经常遇到实体类接收不了封装结果,这时可以另外创建一个类来包括实体类的所有属性和多出来的属性,比如vo)
重写下Employees的toString方法,方便后面查看
多对一
接下来先以简单的多对一关系进行实现,以下面查询结果为例
映射文件,在主配置文件注册这些常规流程就不说了
先构造select语句
再构造resultMap,因为查询的主表是Employees,所以type为Employees类
这段没什么好说的,都是Employees的属性,按照普通的列名-属性名进行映射就行
重点是association标签,关联(association)元素处理“有一个”类型的关系
association标签下又包含四个属性,不过只用关心property和javaType
简单来说
property:你想将另一张表查询的结果集放到哪个属性下,还记得在Employees表下的Jobs jobs属性吗,所以这里property=’jobs’
javaType:接收另一张表结果集的JavaBean
接下来只要完成另一张表自己的列名-属性名的映射就行了
test
查询成功
在该例中,我们完全可以将多表查询等价为嵌套子查询,即先查询jobs表得到结果,在此结果上再查询employees表
因此我们可以修改成另一种形式
实现查询jobs表和employees的SQL语句,jobs直接返回结果,employees表还需要进行映射处理
同样重点在于association标签
property和javaType和上面一样
column和select的功能如下
column:连接键的列名,这里column是必选项,不像前面可以略去,因为要知道通过哪个共同的键进行查询
select:也是必选,不然怎么获得嵌套查询的结果
同样成功查询
一对多
当jobs作为主表时,通过job_id查询employees,显然构成了一对多的关系,但是大部分操作都相似,连SQL语句都是一样的
接下来是关键点resultMap,association标签用于处理“有一个”类型的关联,而处理“有多个”类型的关联就需要用到collection集合标签了,collection标签的属性和association标签大部分相同,需要注意的是collection返回的类型,因为返回的是List,所以javaType得是ArrayList类型(可忽略),而ofType填才是List中元素的类型
在jobs表中实现打印方法(本来不用这么麻烦,但是只取了几个字段,其他字段就不打印了)
成功获取
关于多表查询的结果映射还是比较令人头大,可以参考以下视频,以及强烈建议参考官方文档
【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂
动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,循环,拼接等情况,是比较常用的功能。
比如在一个页面中的表单有几个传输参数(学到MyBatis应该都学过JaveWeb了)
根据传输的参数动态地构造SQL语句
可以发现使用原生的JDBC十分的繁琐,而且还容易漏掉空格逗号造成错误,因此MyBatis提供了强大的动态SQL,让我们摆脱这种麻烦。
if标签是比较常用的功能,最常见情景是根据条件包含 where 子句的一部分
比如想查询工资大于minSalary,小于maxSalary范围的字段
仅传入minSalary
根据日志信息可以看到成功拼接了SQL语句(maxSalary为空)
其实这里写的拼接语句有很大的漏洞,假如minSalary为空但maxSalary不为空
就会拼接成错误的SQL语句导致报错
一种解决方法是在主SQL中添加where 1=1(保证后续的语句肯定执行),拼接SQL统一用逻辑连接符开头
这样SQL语句就不会出错了
另一种方法是使用trim标签,这个就自己去官网看。
choose标签和swith-case语句类似,只会进入一个分支
比如我们可以根据job_id或job_title查询字段,如果一个属性存在,另一个就属性就不使用
为了规范点,以后查询条件都放在where标签内
只用job_title查询(when标签)
同时用两个属性查询,可以看到当排在前面的分支符合后,就算后面有其他分支符合条件也不会进入
默认情况(otherwise标签)
foreach常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach使用难点比if和choose复杂些,主要在于参数的选择,foreach标签内有以下几个参数
使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
比如传入List,通过多个job_id查询多个字段
另外提一下,我们知道形参不同就可以函数重载,但在mybatis这是不允许的,因为标签id必须得唯一
Mybatis的XxxMapper.xml中能否配置重载方法
Mybatis的mapper接口函数重载问题
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率
MyBatis默认定义了两级缓存:一级缓存和二级缓存
一级缓存是默认开启的(SqlSession级别的缓存,也称为本地缓存)
如下,当我们先后分别查询同一字段,因为第一次查询的结构仍在缓存中,所以并不需要再次从数据库查询,大大增加了高并发的效率,因为我们知道数据从内存中读取远比从磁盘IO读取来的快
演示需要用到日志功能,这里不使用log4j,使用MyBatis自带的日志功能换换口味,在主配置文件的setting标签开启就行
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
一级缓存在几种情况下会失效,官方的给的文档说到select语句的结果会被缓存,但是insert、update、delete语句会刷新缓存
如下,在两次查询同一字段中,我们还更新了字段,尽管更新的字段和要查询的字段无关,MyBatis为了保证ACID原则也会再次从数据库查询
对此我们可以看各个标签内的属性,对于select语句,默认使用缓存
对于增删改操作,没有useCache属性,并且flushCache默认为true,所以一执行就会刷新缓存
二级缓存也叫全局缓存,是基于namespace级别的缓存,一个命名空间对应一个缓存(二级缓存并不是来解决一级缓存失效的问题的)
一个会话查询一个缓存,查询的数据会存放在当前会话的一级缓存中,如果该会话关闭,其缓存也会消除。
但我们可以使其保存在二级缓存中,新的会话查询就可以从二级缓存读取,不同的mapper查出的数据会放在自己对应的缓存中
要启用全局的二级缓存,需要现在主配置文件开启。
<setting name="cacheEnabled" value="true"/>
因为二级缓存的作用域是namespace,也就是mapper映射文件,所以哪个映射文件需要二级缓存,在该mapper映射文件中添加< cache >即可
创建两个会话,在第一次会话关闭后,仍然查询相同的字段,很明显可以看到两次查询的对象是相同的。
MyBatis毕竟不是专门做缓存的,所以在一些复杂的情况下就需要用其他更强大的缓存,比如EhCache、Redis。自行了解
MyBatis自定义缓存——Redis实现
MyBatis自定义缓存——EhCache实现
MyBatis底层源码解析 (详细)
目前来说,Mybatis常与Spring、SpringMVC一起组成SSM框架,也是目前主流的MVC框架来完成一个JavaWeb项目的开发,因此学习整合Mybatis和Spring(SpringMVC是Spring下的子框架,整合Spring就行)是十分必要的。
使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。
毫无疑问,MyBatis 和 Spring 两大框架已经成了 Java 互联网技术主流框架组合,它们经受住了大数据量和大批量请求的考验,在互联网系统中得到了广泛的应用。使用 MyBatis-Spring 使得业务层和模型层得到了更好的分离,与此同时,在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。
–《Java EE 互联网轻量级框架整合开发》
篇幅原因这里不进行实现
Spring:
Java学习_Spring_IoC
Java学习_Spring_AOP
MyBatis-Spring
MyBatis 与 Spring 整合
MBG(MyBatis-generator),是MyBatis框架中代码生成器。在此之前,我们先了解逆向工程的概念。
逆向工程(又称逆向技术),是一种产品设计技术再现过程,即对一项目标产品进行逆向分析及研究,从而演绎并得出该产品的处理流程、组织结构、功能特性及技术规格等设计要素,以制作出功能相近,但又不完全一样的产品。逆向工程源于商业及军事领域中的硬件分析。其主要目的是在不能轻易获得必要的生产信息的情况下,直接从成品分析,推导出产品的设计原理。
比如制造一个零件,正向的流程是先用绘图工具比如AutoCAD、UD进行绘制图纸和建模,然后进入生产车间通过车床之类的加工出来。而逆向工程是先有实物,通过扫描实物获取实物的各个参数,在计算机中分析数据,再经过修正处理就可以获得接近实物的图纸,再通过图纸加工出实物,大大减少了设计流程。
在前面使用MyBatis中,我们要根据数据库表创建实体类、DAO层的各个接口以及mapper映射文件,通过逆向工程,我们就可以根据数据库表自动生成这些东西。它只需要很少量的简单配置,就可以完成大量的表到Java对象的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。
MBG的官方文档如下,目前只有英文,估计以后也不会更新了
MyBatis Generator
导入相关依赖(数据库,MyBaits这些依赖就不说了)
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.7version>
dependency>
MGB只负责帮我们生成三类文件,上面也说过了
根据官方文档快速开始,简单来说就是MBG生成的代码可以有几种风格,取决于在其配置文件下< context >如何配置targetRuntime这个属性。
MBG快速运行只需要三步
targetRuntime这个属性提供了四种代码生成策略
这里以MyBatis3生成策略为例(MyBatis3和MyBatis3Simple的targetRuntime属性都是MyBatis3Simple,MyBatis3Simple相较于MyBatis3的配置文件只是少了< sqlMapGenerator >这个用于生成mapper映射文件的标签)
创建配置文件,将文档给的配置复制上去先
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="simple" targetRuntime="MyBatis3Simple">
<jdbcConnection driverClass="org.hsqldb.jdbcDriver"
connectionURL="jdbc:hsqldb:mem:aname" />
<javaModelGenerator targetPackage="example.model" targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="example.mapper" targetProject="src/main/resources"/>
<javaClientGenerator type="XMLMAPPER" targetPackage="example.mapper" targetProject="src/main/java"/>
<table tableName="FooTable" />
context>
generatorConfiguration>
接下来就可以运行MBG了,执行的方法有以下几种,因为我们用XML来配置,所以用下面这种方式执行
把文档给的代码改改(主要是配置文件的路径)执行
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.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);
到目标包查看生成的代码
对于实体类,MBG可以自动将数据库字段变成驼峰式命名
对于DAO层接口和Mapper映射文件,MBG会自动生成最基础的SQL语句
MBG的配置文件中还有很多配置没说,可以自行阅读官方文档和下篇文章
MyBatis——MyBatis Generator插件使用(配置详解)
尽管MBG已经很大程度提高我们的效率,但在使用 MBG 过程中,如果数据库字段变化很频繁,就需要反复重新生成代码,并且由于 MBG 覆盖生成代码和追加方式生成 XML,导致每次重新生成都需要大量的比对修改。除了这个问题外,还有一个问题,仅仅基础的增删改查等方法,就已经产生了大量的 XML 内容,还没有添加一个自己手写的方法,代码可能就已经几百行了。
由于MBG 中定义的大多是常用的单表方法,为了解决前面提到的问题,也为了兼容 MBG 的方法避免项目重构太多,在 MBG 的基础上结合了部分 JPA 注解产生了通用 Mapper。通用 Mapper 可以很简单的让你获取基础的单表方法,也很方便扩展通用方法。使用通用 Mapper 可以极大的提高工作效率。
-摘自MyBatis 为什么需要通用 Mapper ?
官方文档:通用 MAPPER 3
通用mapper也叫tk mapper,通用Mapper需要在Mybatis和Spring的整合上进行集成
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>4.1.5version>
dependency>
导入相关依赖后,需要在Spring的配置文件上进行一些修改,即Mapper扫描配置,正常扫描包的路径是mapper映射文件所在的目录
这里需要修改为通用Mapper的类,其作者为了方便大家,这里只需要将类最前面的org改为tk即可,同时扫描的路径不再是mapper映射文件,而是DAO层接口所在的包
通用Mapper帮助我们省略mapper.xml的编写,想当然必须要花费别的代价,它要求我们的DAO接口继承它提供的BaseMapper接口
这里仍以jobs表为例
实体类直接使用MBG生成的(注意实体类是Job,表是jobs,挖下坑)
继承接口,泛型传入相应的实体类
这里看不出什么,查看Mapper接口就会发现大有文章,Mapper接口又继承了以下接口,看接口的名称也能猜个大概
先看BaseMapper,很显然该接口提供了基本的CRUD功能,一层一层往下看
每个接口看太麻烦,直接看BaseMapper的结构,看它都继承到了些什么方法
为了方便测试这些方法,使用JUnit测试工具来测试
引入依赖
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.1version>
<scope>testscope>
dependency>
我们的接口需要把继承的方法实现,IDEA中Ctrl+I 快捷键就可以批量实现
随后在当前接口快捷键 Ctrl + Shift + t 就可以生成测试文件
最终会在test包下生成测试文件
然后取出相关的bean就可以测试
以简单的selectAll()为例,取出表中所有记录
报错了,因为数据库中没有job这张表,前面故意留的坑,可以看出通用mapper自动找的表应该是与实体类名称一致的表,对于这种情况可以修改数据库表名或实体类名字,但显然不合理,因为这些基本是不动的。
只需要在实体类上加个注解就行
测试成功
同样的如果属性名和列名不一致也要添加相应的注解(下划线会自动转换为相应的驼峰式,所以这里没出错)
假如数据库字段变成驼峰式也对应不上,那肯定会报错
如果有些属性是数据库没有的字段,可以加上@Transient,不参与SQL语句
接下来测试其他方法
selectOne(),查询一个结果,这里需要传入相应实体类,并返回查询结果,传入的参数主要用于封装查询条件的(只能判断’='的条件,对于大于小于这些其他判断条件暂时判断不了)
该方法限制了只返回一个结果,如果查询结果有多个结果会报错
这时得调用select()来接收
selectByPrimaryKey(),根据主键来查询
没查询到结果, 我们检查下SQL语句,发现这一幕,所有字段都根据给的主键值来查询,显然是查询不到的
这是因为通用mapper不知道哪个属性是主键,只能全部注入了,解决方法在主键属性上加上注解
其他方法大同小异,自己去官方文档了解
Mapper 接口大全
BaseMapper接口的功能了解后,现在来看ExampleMapper
ExampleMapper用于QBC查询
QBC(Query By Criteria)是一种面向对象的查询方式,这种查询方式以函数API的方式动态地设置查询条件,组成查询语句。
在前面在用select方法时是直接用实体类封装查询条件,这种方法只能简单地来根据字段是否等于封装条件来查询。如果想查询更复杂些的,比如要查询薪资在某个范围内(minSalary 在通用Mapper中,使用QBC查询先创建Example实例,可以从Example类的结构看出Criteria和Criterion是其内部类,需要通过Example实例获取 大概结构了解后,开始测试。比如想要查询薪资在(4000-5000)或(10000-20000)间的job 该SQL语句分为两部分,所以需要创建两个Criteria 如下,不对Criteria进行链接 该接口的功能用于分页操作 MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 官方文档:MyBatis-Plus
我们看到Criteria提供了大量封装好的查询条件
绝大多数方法需要以下这两种参数类型,显然就是属性名和属性值(仔细看看就会发现该类构造查询条件其实就是拼接字符串来实现的)
SQL语句应为(minSalary >=4000 AND maxSalary<=9000) OR (minSalary >=10000 AND maxSalary<=20000)
为Criteria封装查询条件(GreaterThanOrEqualTo:大于或等于 LessThanOrEqualTo:小于或等于,如果是或逻辑,把and改成or就行)
然后合并两个Criteria需要调用example的or或and
当我看到生成的SQL语句我陷入了沉思,怎么又多出了一个查询条件!
经过几次试验,我猜测example会自动将第一个Criteria的查询条件封装到自己内部,所以example进行逻辑链接时就不用链接第一个了
可以发现封装的条件和参数和第一个Criteria一致
这样的话只用链接第二个Criteria就行了
还有一个坑是example自动链接的第一个Criteria是根据什么来决定的,是根据Criteria创建还是Criteria封装条件后?
我把2放到1前,让2先进行创建
根据参数的参数可以知道是根据创建的顺序来链接了
感觉该作者设计得不太好,不管怎么样还是查询到了结果(个人宁愿自己写SQL语句)
剩下的方法不测试了,只要英语和SQL语句有点基础都能根据方法名了解怎么使用
RowBoundsMapper
需要两个参数,第一个封装查询条件的类,第二个RowBounds用于封装分页的参数
该类的结构十分简单,除了构造器就是get方法
这里就不封装查询条件了
可以看到该方法并不是在SQL语句后面拼接分页逻辑,而是查询所有数据到内存,再到内存中进行分页
MyBatis-Plus
实际上中MP会用的更多,MP是比MBG和通用mapper更强大的工具,其核心功能和上两者相同,所以这里不对MP进行了解,相信大家在学会MBG和通用mapper后,加上文档能很容易上手MP
这些工具固然好用,但还是建议大家先把基本的SQL语句用熟练了再使用工具简化开发