文章目录
1 Mybatis简介
1.1 什么是Mybatis?
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低.
Mybatis官方文档
GitHub地址
持久化是什么
持久化是将程序数据在持久状态和瞬时状态间转换的机制。
- 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
- JDBC就是一种持久化机制。文件IO也是一种持久化机制。
- 在生活中 : 将鲜肉冷藏,吃的时候再解冻的方法也是。将水果做成罐头的方法也是。
为什么需要持久化服务呢?那是由于内存本身的缺陷引起的
- 内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。
- 内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。
持久层又是什么呢
- 完成持久化工作的代码块 . ----> dao层 【DAO (Data Access Object) 数据访问对象】
- 大多数情况下特别是企业级应用,数据持久化往往也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。
- 不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现.
ORM是啥?
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据
那么为什么说mybatis是半自动的ORM框架?
用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多
1.2 Mybais的第一个程序
思路流程:搭建环境–>导入Mybatis—>编写代码—>测试
而在编写代码时只需要通过如下几个步骤,即可用mybatis快速进行持久层的开发
该程序最后的项目文件架构如下:
1、搭建实验数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'偷学者1号','123456'),(2,'偷学者2号','abcdef'),(3,'偷学者3号','987654');
2、打开IDEA,创建一个maven项目
3、导入相关的jar 包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- lombok插件依赖的导入 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<!-- log4j依赖的导入 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
4、配置log4j配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
5、配置数据源db.properties文件
这个要根据自己数据库所在的IP地址与密码
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://192.168.10.104:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8
db.user=root
db.password=12345678
6、编写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>
<!-- 配置文件信息 -->
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 从配置文件中加载属性 -->
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
7、配置MybatisUtils工具类,来加载全局配置文件和生成SqlSessionFactory
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 getSession(){
return sqlSessionFactory.openSession();
}
}
8、创建User实体类
这里为例防止代码冗余,我使用了Lombok插件,不会的可以去搜索如何使用,非常方便。
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private int id;
private String name;
private String pwd;
}
9、编写映射接口:
import com.zjw.bean.User;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
}
10、编写对应的mapper映射文件
<?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.zjw.Dao.UserMapper">
<select id="selectUser" resultType="com.zjw.Dao.User">
select * from user
</select>
</mapper>
11、编写完mapper映射文件后去全局配置文件中去注册
<mappers>
<mapper resource="com/zjw/Dao/UserMapper.xml"/>
</mappers>
12、编写测试类
public class MyTest {
@Test
public void selectUser() {
SqlSession session = MybatisUtils.getSession();
//方法一:
List<User> users = session.selectList("com.zjw.Dao.UserMapper.selectUser");
//方法二:
// UserMapper mapper = session.getMapper(UserMapper.class);
for (User user: users){
System.out.println(user);
}
session.close();
}
}
13、启动测试类,出现结果:
不过根据整个简单的项目代码来看,我们仅仅为了完成一个select操作,好像创建了许多的XML配置文件,那么似乎与他方便的特点相反呀,但其实我们在后续的编写SQL语句就非常的轻松,因为在一个 XML 映射文件中,可以定义无数个映射语句,后面我们只需要在Mapper文件中添加定义,然后在对应的XML文件编写sQL语句就可以了。
总结:
搭建好了一个环境之后,进行SQL操作,就是重复以下几个步骤:
- 编写mapper接口,声名SQL的方法
- 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数
- 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件
- 通过全局配置文件,创建SqlSessionFactory
- 每次进行CRUD时,通过SqlSessionFactory创建一个SqlSession
- 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数
1.3 搭建项目中容易出现的问题
1、配置文件中namespace中的名称为对应Mapper接口或者Dao接口的完整包名不一致
例如Mapper的映射文件
以及全局配置文件中注册的mapper地址
2、db.properties配置的属性名字与Mybatis全局配置名字不一致
3、出现找不到mapper映射文件的错误
这样的情况出现的原因我知道的有两种情况
- 注册的时候文件分割符使用的是.而不是/
- 或者去生成的target的文件夹下看看有没有mapper映射文件
如果没有的话可以直接复制粘过去就行,然后在pom文件中加入以下代码防止之后再次出现这种情况,我之前给出的代码中已经加上了这个,不要重复。
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
4、明明数据库中的用户名与密码均填写正确却出现连接超时的情况
可以看看自己的这个是true还是false
1.4 项目搭建过程中的一些详细说明
1.4.1 全局配置文件的标签说明与顺序
注意在全局配置文件中,各个标签要按照如下顺序进行配置,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的
<configuration>
<!-- 配置顺序如下
properties
settings
typeAliases
typeHandlers
objectFactory
plugins
environments
environment
transactionManager
dataSource
mappers
-->
</configuration>
各个子标签说明如下:
-
<properties>
一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息。例如我们前面给出的mybatis-config.xml中的代码
-
<settings>
用来开启或关闭mybatis的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>
来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>
来开启二级缓存 -
<typeAliases>
在mapper.xml中需要使用parameterType
和resultType
属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为User类,则resultType属性要写com.zjw.Dao.User
,这太长了,所以可以用别名来简化书写,比如我在mybatis-config.xml
中加入以下代码:<typeAliases> <typeAlias type="com.zjw.Pojo.User" alias="user"/> </typeAliases>
然后修改UserMapper.xml代码,从而达到代码不那么繁琐的目的:
当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式<typeAliases> <package name="com.zjw.bean"/> </typeAliases>
如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名。
-
<typeHandlers>
(不经常用)
用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。这个标签用的不多 -
<objectFactory>
(不经常用)
mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多 -
<plugins>
可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。在mybatis底层,运用了责任链模式+动态代理去实现插件的功能。<!-- PageHelper 分页插件 --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> </plugin> </plugins>
-
<environments>
用来配置数据源 -
<mappers>
用来配置mapper.xml映射文件,这些xml文件里都是SQL语句。而在实际工作中,一般我们会将一张表的SQL操作封装在一个mapper.xml中 ,可能有许多张表需要操作,那么我们是不是要在<mappers>
标签下写多个<mapper>
标签呢?其实不用,还有第三种加载mapper的方法,使用<package>
标签<mappers> <package name="com.zjw.Dao"/> </mappers>
这样就会自动加载com.zjw.Dao包下的所有mapper,这种方式需要将mapper接口文件和mapper.xml文件都放在com.zjw.Daor包下,且接口文件和xml文件的文件名要一致。
三种加载mapper的方式总结- 加载普通的xml文件,传入xml的相对路径(相对于类路径)
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
- 使用完全限定资源定位符(URL)
<!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers>
- 使用映射器接口实现类的完全限定类名(需要配置文件名称和接口名称一致,并且位于同一目录下)
<!-- 使用映射器接口实现类的完全限定类名 需要配置文件名称和接口名称一致,并且位于同一目录下 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers>
1.4.2 mapper接口和mapper.xml之间遵循的规则
mapper接口和mapper.xml之间需要遵循一定规则,才能成功的让mybatis将mapper接口和mapper.xml绑定起来
- mapper接口的全限定名,要和mapper.xml的namespace属性一致
- mapper接口中的方法名要和mapper.xml中的SQL标签的id一致
- mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致
- mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致
1.4.3 SqlSession的作用域(Scope)和生命周期
理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。
我们可以先画一个流程图,分析一下Mybatis的执行过程!
作用域理解
- SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
- SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
- 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。 - 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。所以 SqlSession 的最佳的作用域是请求或方法作用域。