0%

Mybatis入门

框架

首先Mybatis是一套框架。框架是为了解决软件开发中遇到的一些问题而生的,框架一般会封装很多细节,能让开发者可以减少工作量、提高工作效率。阿里巴巴内部对这个框架用了很多。

  • 表现层:代表框架:SpringMVC,用以展示数据。
  • 业务逻辑层:主要靠servicejavabean来完成,用来完成业务的处理。
  • 持久层:mybatis,和数据库进行交互,向外提供一个DAO接口

目前在Java中,原生的技术有JDBC,包含了ConnectionPreparedStatement之类的,然后当然有一些简单封装的工具类,比如ApacheDBUtils等。

mybatis在内部封装了jdbc,使得开发者只需要关注于sql语句本身,其它的加载驱动、创建连接等,我们都可以通过注解或者配置xml文件来进行处理。还有一个就是能够直接返回一个对象,便于开发人员直接操作java对象。

之前的流程

我们连接数据库,大体可以分成这几个步骤:

  • 配置数据库的信息
  • 连接到数据库
  • 对数据库进行操作
  • 获取了对应的数据之后进行处理
  • 一些善后工作等

当然之前我们都是通过编写对应的代码,一些数据库的信息都是直接写死在了对应的代码里面,这样耦合很高,而且也不安全,需要切换的时候也非常不方便。

环境搭建

首先因为是和数据库相关的框架,相关的数据库必须得有,所以创建一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 创建数据库
CREATE DATABASE IF NOT EXISTS db_mybatis CHARACTER SET utf8;
-- 使用数据库
USE db_mybatis;
-- 创建数据表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 添加记录
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values
(41,'老王','2018-02-27 17:47:08','男','北京'),
(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),
(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),
(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),
(46,'老王','2018-03-07 17:37:26','男','北京'),
(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');

数据准备好了之后,就可以开始编写代码了,具体步骤如下:

创建项目并初始化

创建空maven工程并且写好数据库驱动依赖和mybatis的依赖。

首先的pom里面增加对应的依赖(还有mysql的驱动、Junit这些的自己添加):

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>

并且在类路径下(一般推荐在resource下,如果不是在resource下面,那么默认情况下是不会打包进项目中的,这点切记,当然可以通过配置maven来让对应的后缀的文件加入到类路径中)创建一个SqlMapConfig.xml(当然叫什么其实是随意的,反正代码能找到就行),然后在里面配置好对应的数据库相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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>
<environments default="mysql">
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"/>
<!-- 连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost/df?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

<!-- 对应的sql映射文件所在位置 -->
<mappers>
<mapper resource="com/df/dao/IDeviceInfoDao.xml"/>
</mappers>
<!-- 如果上面的内容没有的话,就会报Type interface cn.clp.mapper.UserMapper is not known to the MapperRegistry.-->

</configuration>

到这里准备工作就做好了。PS:上面那个<mappers>里面的映射关系是用来管理对应的sql语句的。

创建dao的接口和实体类

就是到项目下面,创建一个接口,这个接口后面就只需要定义对应的方法,比如查找的方法就可以了。不需要任何的实现方法,这里也是mybatis最与众不同的地方,它使用了配置文件来代替实现类完成对应的增删改查功能。

为对应的数据库封装对应的对象,因为我们最后肯定是要把数据库的内容读取出来到对象的嘛,那就先创建好这些对象,然后这些对象也可以在上面创建的接口中被使用。

创建对应的映射文件

这个文件就是上面初始化的配置里面mapper所对应的文件。在这个文件里面写好对应的sql语句,然后就可以执行里面的sql语句了。所以可以把这个文件当成是一座桥梁,把之前定义的接口和sql语句联系在了一起,而这个sql语句其实就是接口的实现函数。

一个demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.df.df_back.dao.IDeviceInfoDao">

<resultMap id="ApplicationResultMap" type="com.df.df_back.domain.Application">
<result property="id" column="id"/>
<result property="applicationInfo" column="application_info"/>
</resultMap>
<select id="findAllApplication" resultMap="ApplicationResultMap">
select id, application_info
from application;
</select>
</mapper>

可以看到上面我指定了namespace就是对应的接口,而里面的就是一个select语句。你看它为什么叫mapper,因为它就是连接了接口和对应的实现类。

到这里就完成了,值得注意的是:

  • Mybatis中,持久层的操作接口名称和映射文件也叫Mapper,所以UserMapperIUserDao是一样的
  • 映射配置文件的mapper标签namespace属性的取值必须是mapper接口(即DAO接口)的全限定类名
  • 映射配置文件的操作配置,id 属性的取值必须是mapper接口的方法名

看上去是不是一头雾水?什么破玩意儿啊,我接下来用我自己的语言组织下。

首先,你要连接数据库,那么必须要指定驱动、url、用户名和密码,这是肯定省不了的吧?这就对应了上面说的主配置文件。然后对于一个类的方法,你首先必须找到一条sql语句与之对应吧?这个就是第二个配置文件的作用,它让一条sql语句和一个接口(当然也可以是类)中的方法产生了联系。而我们之后会有许许多多的类,也就是会有很多的xml配置文件,这些配置文件需要将自己所在的位置写入到主配置文件中,这样就完成了。

还觉得麻烦吗?对,反正我觉得很麻烦,那怎么办呢?用注解来简化下操作呗。

分析和还原

首先这是主要的一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception {
// 1. 读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 通过配置文件创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
// 3. 获取 SqlSession 对象
SqlSession sqlSession = factory.openSession();
// 4. 使用 SqlSession 创建 Mapper 的代理对象
IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
// 5. 使用代理对象执行查询
List<User> users = iUserDao.findAll();
for (User user : users) {
System.out.println(user.getUsername());
}
// 6. 释放资源
sqlSession.close();
is.close();
}

我们简单分析下,就可以自己来完成这个框架的简单替换了(即自己完成mybatis的核心功能)。一般来说,实际中我们推荐前三步使用工具类进行封装,这样需要的时候可以直接返回sqlSession进行使用。

整体流程是这样的:配置文件 -> SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession ->操作数据库

  1. 读取xml配置我们可以用dom4j来完成。为了简便,可以直接硬编码….
  2. 创建工厂这步,我们只需要用到工厂模式就可以很轻松的完成啦
  3. 动态代理技术

首先,我们对主配置文件中的四个信息(driver、url、username和password)进行封装,构成一个configuration的类,然后构建一个工具类,能够获取connection对象:

1
2
3
4
5
6
7
public class DataSourceUtil {
public static Connection getConnection(Configuration configuration) throws ClassNotFoundException, SQLException {
Class.forName(configuration.getDriver());
Connection connection = DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
return connection;
}
}

本来是想用连接池的,但是这样会增加复杂度,后期可以修改下。

其次,我们封装一下Map<String, Mapper> mappers这个数据结构:它的键是全限定类名+方法名,然后Mapper里面是一个sql语句和一个返回类型(也就是数据库表中的数据所对应的类名)。这样我们就能知道每个方法所对应的sql语句以及应该给查询出来的语句赋值到哪个对象上去了。

然后,也是最最重要的一部分,就是使用动态代理的方法来加强函数的功能。这部分就是在getMapper那个方法中,为其加入数据库查询的部分,然后加入返回值的对象即可。

CURD

插入操作

定义一个函数来保存一个对象到数据库里,只需要在接口中创建一个方法,比如是void saveUser(User user);,然后在配置文件里面加入一个insert标签:

1
2
3
<insert id="saveUser" parameterType="cn.chenlangping.domain.User">
INSERT INTO user (username,address,sex,birthday) values( #{username},#{address},#{sex},#{birthday})
</insert>

其中id是方法名字,而第二个参数是真正参数的全限定类名。最后在#{}内的内容,就是属性的名字。最后使用的时候不要忘记,默认是关闭了自动提交事务的,所以记得手动提交下事务

更新操作

一样的方法,也是在接口中创建一个方法,然后是一样的,只是标签有点不同。

1
2
3
<update id="updateUser" parameterType="cn.chenlangping.domain.User">
UPDATE user SET username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id = #{id}
</update>

删除操作

删除操作有点特殊,因为我们只需要知道id即可删除对象,而且仅仅需要一个参数,所以仅仅是一个占位符而已。

1
2
3
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE from user where id = #{id}
</delete>

map的妙用

上面的一些我们在查询的时候,需要自己构造一个对应的对象,如果我们仅仅需要这个对象的某一些属性来作为参数查询,而这个对象的属性非常多,那么就可以使用map来进行简化操作了。

配置文件详解

核心配置文件

环境配置(environments)

MyBatis 可以配置成适应多种环境,现实中最常用的就是测试环境和生产环境的一键切换。尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

属性(properties)

属性可以在很多地方定义,然后就可以被框架所使用。当然会存在多个属性之间相互覆盖的问题,它们之间的顺序是这样的:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

设置(settings)

设置我们一般只会使用其中的缓存启用、延迟加载等少量设置,需要的时候再进行调试就可以了。

类型别名(typeAliases)

如果不想写很长的全限定类名,那么就可以使用它来简化操作。可以一个类一个类来自己定义;当然也可以对一个包下的所有都去掉它们的全限定类名。这里需要注意的是,本来不同的包下可以存在相同类名的类;但是由于去掉了包名,所以导致类名冲突,这样子mybatis的处理方式是抛出异常。

映射器(mappers)

我们可以指定对应的xml文件的位置来找到映射器,也可以通过使用映射器接口实现类的完全限定类名、还可以通过指定包名。

这里需要注意的是,如果通过类名来找到对应的映射器,那么xml文件和类名需要完全一致才可以。

属性名和字段名不一致

对象的对象

比如我现在用另外一个对象封装了user,然后想利用user的name来进行查询,那么可以使用一个简洁的语法,即OGNL(Object Graphic Navigation Language)来进行处理,简单来说就是使用对象名.属性即可访问成功。比如我要访问user的username属性,我不再需要user.getUsername()方法了,而是只需要输入user.username即可。

其实上面已经在用了,只不过由于只有一层,所以不明显。

如果实体类属性和数据库列名不一致呢?

前面我们都是让类的属性和数据库列名相同,这样是为了方便建立对象,那么如果就是不一样呢?有两种解决办法,第一种就是用数据库的别名,第二种当然是进行配置啦。

1
2
3
4
5
6
<resultMap id="whatever" type="cn.chenlangping.domain.User">
<!-- 主键 -->
<id property="java类中的名字" column="数据库中的列名"/>
<!-- 非主键 -->
<result property="java类中的名字" column="数据库中的列名"/>
</resultMap>

在select等语句上面写上这块,然后在update啊这种DML语句中,把resultType删了换成resultMap并且写上id,这样mybatis就知道了。

根据对象条件

假设我传入了一个user对象,而这个对象中我只设置了名字,也就是我希望能根据user中存在的信息来找用户,那么应该怎么做?显然上面的这些都不行了,需要引入特殊的标签<if>

1
2
3
4
5
6
<select id="findUserByCondition" parameterType="cn.chenlangping.domain.User" resultType="cn.chenlangping.domain.User">
SELECT * FROM user where 1=1
<if test="username!=null">
and username = #{username}
</if>
</select>

上面的例子就能很好的说明问题,如果传入的user对象的username不是空(按照java的语法),那么把下面if标签里面的内容拼接到原始的那句话上面。

仔细看上面的内容,你会发现有个1=1 看上去是不是贼不舒服?mybatis为我们提供了<where>标签来解决,所以修改一下就会这样:

1
2
3
4
5
6
7
8
9
<select id="findUserByCondition" parameterType="cn.chenlangping.domain.User"
resultType="cn.chenlangping.domain.User">
SELECT * FROM user
<where>
<if test="username!=null">
username = #{username}
</if>
</where>
</select>

实际中可能还会碰到这种需求,比如select * from user where id in (1,2,3);,这种sql语句在mybatis中应该怎么写呢?用<foreach>标签即可。

连接池

连接池首先它必须是线程安全的,不然会导致两个线程能够拿到同一个。而且它本身也是一个队列,这样就能够循环使用各个连接啦。而mybatis当然也提供了连接池的配置,在主配置文件里的datasource标签中就可以进行配置,type就是用来表示是采用什么方式来进行连接的,它可以有以下三种取值:

  • POOLED:采用传统Java datasource中的连接池。mybatis有针对这个规范的实现。
  • UNPOOLED:不用连接池,虽然它实现了javax.sql.datasource接口,但是实际上它每次都是单个连接,并没有使用所谓的连接池。
  • JNDI:采用服务器提供的JNDI技术实现,来获取datasource对象,不同服务器不同;如果不是web工程,将无法使用。比如tomcat使用的是叫dbcp连接池。

多表关联

假设现在有一张用户表,一张账户表;两者是多对一的关系,即一个用户可以有多个账户。显然根据数据库的知识我们可以知道,账户表中需要有一个外键来指向用户表。

1
2
3
4
5
6
7
8
9
10
11
12
-- 创建账户表,外键为uid,关联用户表的id
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL COMMENT '编号',
`uid` int(11) default NULL COMMENT '用户编号',
`money` double default NULL COMMENT '金额',
PRIMARY KEY (`id`),
KEY `FK_Reference_8` (`uid`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 导入账户数据
insert into `account`(`id`,`uid`,`money`) values (1,46,1000),(2,45,1000),(3,46,2000);

然后是在Java中,必然我们需要建立两个类来分别对应用户和账户。同时针对这两个类我们也需要有两个配置文件。

最后再新建一个类,能够存入查询出来的数据。

缓存

适用于缓存的数据:不经常改变、而且改变的话对最终结果影响不大的数据才使用用缓存。

一级缓存

sqlSession本身就提供了一块缓存区域,该区域本质上是一块Map。可以通过sqlSession.clearCache()方法来清除缓存。

二级缓存

sqlSessionFactory也提供了缓存,叫做二级缓存,但是默认是不支持的。需要在主配置文件中和对应的类配置文件中配置,然后还需要让语句支持。二级缓存中存放的是数据,而不是对象。