Mybatis入门

框架

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

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

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

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

环境搭建

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建数据库
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','女','北京修正');

数据准备好了之后,就可以开始编写代码了。

  1. 创建空maven工程并且写好数据库驱动依赖和mabatis。
  2. 创建dao的接口和实体类。
  3. 创建mybatis的主配置文件,配置映射文件。
  4. 创建映射配置文件。

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

  • 在 Mybatis 中,持久层的操作接口名称和映射文件也叫 Mapper ,所以 UserMapper 和 IUserDao 是一样的
  • 映射配置文件的 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的核心功能)。

  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>

对象的对象

比如我现在用另外一个对象封装了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也提供了缓存,叫做二级缓存,但是默认是不支持的。需要在主配置文件中和对应的类配置文件中配置,然后还需要让语句支持。二级缓存中存放的是数据,而不是对象。