Java
Java Web框架

MyBatis(1)配置及简单入门

简介:MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

1. Mybatis简介

MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。

每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。

用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。

MyBatis有以下优点:

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现;
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多;
  • 解除sql与程序代码的耦合:通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性;
  • 提供映射标签,支持对象与数据库的orm字段关系映射;
  • 提供对象关系映射标签,支持对象关系组建维护;
  • 提供xml标签,支持编写动态sql。

2. Mybatis架构

.1.MyBatis架构

下面作简要概述:

  1. SqlMapConfig.xml,此文件作为Mybatis的全局配置文件,配置了Mybatis的运行环境等信息。mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句,此文件需要在SqlMapConfig.xml中加载;
  2. 通过Mybatis环境等配置信息构造SqlSessionFactory,即会话工厂;
  3. 由会话工厂创建SqlSession即会话,操作数据库需要通过SqlSession进行;
  4. Mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
  5. MappedStatement也是Mybatis一个底层封装对象,它包装了Mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是MappedStatement的id;
  6. MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、POJO,Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程中对preparedStatement设置参数;
  7. MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射过程相当于JDBC编程中对结果的解析处理过程。

3. Mybatis环境搭建

我们使用Maven来搭建MyBatis的测试环境。首先创建一个Maven工程,然后引入下面的依赖项即可:

  • <dependencies>
  • <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
  • <dependency>
  • <groupId>org.mybatis</groupId>
  • <artifactId>mybatis</artifactId>
  • <version>3.2.7</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  • <dependency>
  • <groupId>mysql</groupId>
  • <artifactId>mysql-connector-java</artifactId>
  • <version>5.1.46</version>
  • </dependency>
  • <dependency>
  • <groupId>junit</groupId>
  • <artifactId>junit</artifactId>
  • <version>4.12</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
  • <dependency>
  • <groupId>org.apache.logging.log4j</groupId>
  • <artifactId>log4j-core</artifactId>
  • <version>2.0</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
  • <dependency>
  • <groupId>org.apache.logging.log4j</groupId>
  • <artifactId>log4j-api</artifactId>
  • <version>2.0</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/log4j/log4j -->
  • <dependency>
  • <groupId>log4j</groupId>
  • <artifactId>log4j</artifactId>
  • <version>1.2.17</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
  • <dependency>
  • <groupId>org.slf4j</groupId>
  • <artifactId>slf4j-log4j12</artifactId>
  • <version>1.7.5</version>
  • <scope>test</scope>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
  • <dependency>
  • <groupId>org.slf4j</groupId>
  • <artifactId>slf4j-api</artifactId>
  • <version>1.7.5</version>
  • </dependency>
  • <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
  • <dependency>
  • <groupId>commons-logging</groupId>
  • <artifactId>commons-logging</artifactId>
  • <version>1.1.1</version>
  • </dependency>
  • <dependency>
  • <groupId>junit</groupId>
  • <artifactId>junit</artifactId>
  • <version>4.12</version>
  • </dependency>
  • </dependencies>

接下来,我们在数据库中创建两张表,并填入数据,相应的SQL脚本如下:

  • /*
  • Navicat MySQL Data Transfer
  • Source Server : localhost_3306
  • Source Server Version : 50521
  • Source Host : localhost:3306
  • Source Database : mybatis
  • Target Server Type : MYSQL
  • Target Server Version : 50521
  • File Encoding : 65001
  • Date: 2015-04-09 16:03:53
  • */
  • SET FOREIGN_KEY_CHECKS=0;
  • -- ----------------------------
  • -- Table structure for `order`
  • -- ----------------------------
  • DROP TABLE IF EXISTS `order`;
  • CREATE TABLE `order` (
  • `id` int(11) NOT NULL AUTO_INCREMENT,
  • `user_id` int(11) NOT NULL COMMENT '下单用户id',
  • `number` varchar(32) NOT NULL COMMENT '订单号',
  • `createtime` datetime NOT NULL COMMENT '创建订单时间',
  • `note` varchar(100) DEFAULT NULL COMMENT '备注',
  • PRIMARY KEY (`id`),
  • KEY `FK_order_1` (`user_id`),
  • CONSTRAINT `FK_order_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
  • ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
  • -- ----------------------------
  • -- Records of order
  • -- ----------------------------
  • INSERT INTO `order` VALUES ('3', '1', '1000010', '2015-02-04 13:22:35', null);
  • INSERT INTO `order` VALUES ('4', '1', '1000011', '2015-02-03 13:22:41', null);
  • INSERT INTO `order` VALUES ('5', '10', '1000012', '2015-02-12 16:13:23', null);
  • -- ----------------------------
  • -- Table structure for `user`
  • -- ----------------------------
  • DROP TABLE IF EXISTS `user`;
  • CREATE TABLE `user` (
  • `id` int(11) NOT NULL AUTO_INCREMENT,
  • `username` varchar(32) NOT NULL COMMENT '用户名称',
  • `birthday` date DEFAULT NULL COMMENT '生日',
  • `gender` char(1) DEFAULT NULL COMMENT '性别',
  • `address` varchar(256) DEFAULT NULL COMMENT '地址',
  • PRIMARY KEY (`id`)
  • ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
  • -- ----------------------------
  • -- Records of user
  • -- ----------------------------
  • INSERT INTO `user` VALUES ('1', '王五', null, '2', null);
  • INSERT INTO `user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
  • INSERT INTO `user` VALUES ('16', '张小明', null, '1', '上海市');
  • INSERT INTO `user` VALUES ('22', '陈小明', null, '1', '广州市');
  • INSERT INTO `user` VALUES ('24', '张三丰', null, '1', '深圳市');
  • INSERT INTO `user` VALUES ('25', '陈小明', null, '1', '天津市');
  • INSERT INTO `user` VALUES ('26', '王五', null, null, null);

创建表后,我们还需要在resources目录下创建一个SqlMapConfig.xml文件,配置基本的数据库信息,内容如下:

  • <?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整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <transactionManager type="JDBC" />
  • <!-- 数据库连接池 -->
  • <dataSource type="POOLED">
  • <property name="driver" value="com.mysql.jdbc.Driver" />
  • <property name="url"
  • value="jdbc:mysql://localhost:3306/javaeetest?characterEncoding=utf-8" />
  • <property name="username" value="root" />
  • <property name="password" value="12345678" />
  • </dataSource>
  • </environment>
  • </environments>
  • </configuration>

我们根据users表创建一个User的POJO类,代码如下:

  • package com.coderap.mybatis.pojo;
  • import java.io.Serializable;
  • import java.util.Date;
  • public class User implements Serializable {
  • private static final long serialVersionUID = 1L;
  • private Integer id;
  • private String username;// 用户姓名
  • private String gender;// 性别
  • private Date birthday;// 生日
  • private String address;// 地址
  • public Integer getId() {
  • return id;
  • }
  • public void setId(Integer id) {
  • this.id = id;
  • }
  • public String getUsername() {
  • return username;
  • }
  • public void setUsername(String username) {
  • this.username = username;
  • }
  • public String getGender() {
  • return gender;
  • }
  • public void setGender(String gender) {
  • this.gender = gender;
  • }
  • public Date getBirthday() {
  • return birthday;
  • }
  • public void setBirthday(Date birthday) {
  • this.birthday = birthday;
  • }
  • public String getAddress() {
  • return address;
  • }
  • public void setAddress(String address) {
  • this.address = address;
  • }
  • @Override
  • public String toString() {
  • return "User [id=" + id + ", username=" + username + ", gender=" + gender
  • + ", birthday=" + birthday + ", address=" + address + "]";
  • }
  • }

有了User类,我们就需要为User类定义一个Mapper映射文件了,在resources目录下创建User.xml文件,内容如下:

  • <?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="user">
  • </mapper>

在上述文件中,我们定义了一个mapper根元素,这个元素有一个namespace属性,该属性是用于标识命名空间的,因为我们的工程中可能有多个Mapper文件,每个文件又拥有多个查询语句,为了区分开这些Mapper文件,可以采用命名空间的方式。

注:为了查看测试过程中的执行情况,我们还需要log4j的配置文件,配置如下:

  • # Global logging configuration
  • log4j.rootLogger=DEBUG, stdout
  • # Console output...
  • log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  • log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  • log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

有了以上的环境,就可以开始进行测试了。

4. Mybatis入门查询

4.1. 通过ID查询

我们使用单元测试来快速进行查询测试。

根据Mybatis的架构图,我们首先通过加载SqlMapConfig.xml的方式获取一个SQLSessionFactory对象,然后根据SQLSessionFactory来获取SqlSession对象,才能通过SqlSession对象来进行查询,代码如下:

  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 获取SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();

接下里,我们需要在User类的Mapper文件中编写查询语句,即在User.xml文件添加以下内容:

  • <?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="user">
  • <!-- 通过ID查询一个用户 -->
  • <select id="findUserById" parameterType="Integer" resultType="com.coderap.mybatis.pojo.User">
  • SELECT * FROM user WHERE id = #{value}
  • </select>
  • </mapper>

在上面的新增代码中,我们定义了一个新的标签select,即用于编写SQL语句的标签。在Mybatis中,定义了如select、insert、delete、update等标签用于标识对应的SQL操作。同时,select标签有一个id属性,该属性是用于唯一确定这条SQL语句的标识,我们在编写代码时可以根据上文提到的namespace和该id属性确定需要使用的SQL语句;另外还有一个parameterType属性,这个属性是用于标识SQL语句的入参类型,我们可以观察在标签体中的SQL语句,会发现该语句是一条简单的SQL查询数据,其中用到了where子句来根据id查询,而id的值是需要传入的,对应的即是入参,因此,该入参的类型就是Integer类型。

有了上面定义的SQL语句,我们就可以进行查询了,完整的测试代码如下:

  • @Test
  • public void findUserById() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • User user = sqlSession.selectOne("user.findUserById", 10);
  • System.out.println(user);
  • }

可以发现,通过SqlSession对象的selectOne方法,传入namespaceid唯一标识,以及入参即可查询出对应的数据,执行结果如下:

  • DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
  • DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
  • DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
  • DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
  • DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
  • DEBUG [main] - Opening JDBC Connection
  • DEBUG [main] - Created connection 798244209.
  • DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71]
  • DEBUG [main] - ==> Preparing: select * from user where id = ?
  • DEBUG [main] - ==> Parameters: 10(Integer)
  • DEBUG [main] - <== Total: 1
  • User [id=10, username=张三, gender=1, birthday=Thu Jul 10 00:00:00 CST 2014, address=北京市]
  • Process finished with exit code 0

通过打印语句,我们可以发现,Mybatis给我们设置了自动提交事务的方式,同时还打印出了准备的SQL语句、入参类型及值,并且顺利的查询出数据了。

4.2. 通过用户名模糊查询

有了以上的例子,接下来的测试就很方便了。比如我们需要通过用户名来模糊查询,则需要的select语句如下:

  • <select id="findUserByUsername" parameterType="String" resultType="com.coderap.mybatis.pojo.User">
  • SELECT * FROM user WHERE username LIKE '%${value}%'
  • </select>

其中,需要注意的是#{}${}的区别:

  • #{}:表示字符串,转换后的数据两边带有单引号;
  • ${}:表示取值,转换后的数据两边没有单引号。

上面的查询语句还可以写作:

  • <!-- 根据用户名模糊查询 -->
  • <select id="findUserByUsername" parameterType="String" resultType="com.coderap.mybatis.pojo.User">
  • SELECT * FROM user WHERE username LIKE "%"#{value}"%"
  • </select>

需要注意的是,使用${}时,其中的属性名必须传入value,而#{}模式下属性名可以随意命名。

然后我们需要编写Java代码,如下:

  • @Test
  • public void findUserByUsername() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • List<User> users = sqlSession.selectList("user.findUserByUsername", "五");
  • for (User user : users) {
  • System.out.println(user);
  • }
  • }

运行测试代码,结果如下:

  • DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE username LIKE '%五%'
  • DEBUG [main] - ==> Parameters:
  • DEBUG [main] - <== Total: 2
  • User [id=1, username=王五, gender=2, birthday=null, address=null]
  • User [id=26, username=王五, gender=null, birthday=null, address=null]
  • Process finished with exit code 0

4.3. 添加用户

对应的Mapper内容如下:

  • <!-- 添加用户 -->
  • <insert id="insertUser" parameterType="com.coderap.mybatis.pojo.User">
  • INSERT INTO user (username, birthday, address, gender)
  • VALUES(#{username}, #{birthday}, #{address}, #{gender})
  • </insert>

Java代码如下:

  • @Test
  • public void insertUser() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • User user = new User();
  • user.setUsername("李四");
  • user.setBirthday(new Date());
  • user.setGender("男");
  • user.setAddress("重庆市");
  • int result = sqlSession.insert("user.insertUser", user);
  • sqlSession.commit();
  • System.out.println(result);
  • }

需要注意的是,insert操作需要手动使用sqlSession.commit();提交事务。上述代码执行结果如下:

  • DEBUG [main] - ==> Preparing: INSERT INTO user (username, birthday, address, gender) VALUES(?, ?, ?, ?)
  • DEBUG [main] - ==> Parameters: 李四(String), 2018-03-31 15:03:47.255(Timestamp), 重庆市(String), 男(String)
  • DEBUG [main] - <== Updates: 1
  • DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3043fe0e]
  • 1
  • Process finished with exit code 0

该语句执行的返回结果为影响的行数。

4.4. 添加用户并返回ID

在一些需求中,我们添加完用户后,可能在接下来的业务需要使用这个新用户的ID,这就需要在插入操作后获取插入数据的ID值,这个需求可以通过MySQL提供的select LAST_INSERT_ID()功能来实现。我们编写以下代码,与之前添加用户的代码相比只添加了一个selectKey标签用于返回插入用户的ID:

  • <!-- 添加用户并返回ID -->
  • <insert id="insertUserAndReturnID" parameterType="com.coderap.mybatis.pojo.User">
  • <selectKey keyProperty="id" resultType="Integer" order="AFTER">
  • LAST_INSERT_ID()
  • </selectKey>
  • INSERT INTO user (username, birthday, address, gender)
  • VALUES(#{username}, #{birthday}, #{address}, #{gender})
  • </insert>

上面的映射数据,可以在插入用户之后,自动将id值赋值给传入的User对象,这样一来在接下来的代码中就可以直接使用该User对象的id属性了:

  • @Test
  • public void insertUserAndReturnID() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • User user = new User();
  • user.setUsername("李四");
  • user.setBirthday(new Date());
  • user.setGender("男");
  • user.setAddress("重庆市");
  • int result = sqlSession.insert("user.insertUserAndReturnID", user);
  • sqlSession.commit();
  • System.out.println("新增用户的ID为:" + user.getId());
  • }

运行结果如下:

  • DEBUG [main] - ==> Preparing: INSERT INTO user (username, birthday, address, gender) VALUES(?, ?, ?, ?)
  • DEBUG [main] - ==> Parameters: 李四(String), 2018-03-31 15:23:02.025(Timestamp), 重庆市(String), 男(String)
  • DEBUG [main] - <== Updates: 1
  • DEBUG [main] - ==> Preparing: select LAST_INSERT_ID()
  • DEBUG [main] - ==> Parameters:
  • DEBUG [main] - <== Total: 1
  • DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@bd8db5a]
  • 新增用户的ID为:29
  • Process finished with exit code 0

4.5. 根据用户ID更新用户

对应的Mapper代码如下:

  • <!-- 根据用户Id修改用户 -->
  • <update id="updateUserById" parameterType="com.coderap.mybatis.pojo.User">
  • UPDATE user
  • SET username = #{username}, gender = #{gender}, birthday = #{birthday}, address = #{address}
  • WHERE id = #{id}
  • </update>

测试Java代码如下:

  • @Test
  • public void updateUserByID() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • User user = new User();
  • user.setId(29);
  • user.setUsername("李五");
  • user.setBirthday(new Date());
  • user.setGender("女");
  • user.setAddress("成都市");
  • int result = sqlSession.update("user.updateUserById", user);
  • sqlSession.commit();
  • }

运行结果如下:

  • DEBUG [main] - ==> Preparing: UPDATE user SET username = ?, gender = ?, birthday = ?, address = ? WHERE id = ?
  • DEBUG [main] - ==> Parameters: 李五(String), 女(String), 2018-03-31 15:31:57.455(Timestamp), 成都市(String), 29(Integer)
  • DEBUG [main] - <== Updates: 1
  • DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@bd8db5a]
  • Process finished with exit code 0

可以查看数据库数据,确实已被修改。

4.6. 根据用户ID删除用户

对应的Mapper代码如下:

  • <!-- 根据用户Id删除用户 -->
  • <delete id="deleteUserById" parameterType="Integer">
  • DELETE FROM user
  • WHERE id = #{value}
  • </delete>

测试Java代码如下:

  • @Test
  • public void deleteUserByID() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 执行SQL语句
  • int result = sqlSession.delete("user.deleteUserById", 29);
  • sqlSession.commit();
  • }

运行结果如下:

  • DEBUG [main] - ==> Preparing: DELETE FROM user WHERE id = ?
  • DEBUG [main] - ==> Parameters: 29(Integer)
  • DEBUG [main] - <== Updates: 1
  • DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@bd8db5a]
  • Process finished with exit code 0

可以查看数据库数据,确实已被删除。

5. 使用Mapper动态代理

在一般的开发流程中,我们会有Dao层专门处理与数据库的交互,传统模式下,我们需要定义Dao接口,并且实现Dao接口,在实现类中编写操作数据库的代码,与Mybatis的结合一般如下:

  • // Dao接口
  • package com.coderap.mybatis.dao;
  • import com.coderap.mybatis.pojo.User;
  • public interface UserDao {
  • public User selectUserById(Integer id);
  • }
  • // Dao接口实现类
  • package com.coderap.mybatis.dao;
  • import com.coderap.mybatis.pojo.User;
  • import org.apache.ibatis.session.SqlSession;
  • import org.apache.ibatis.session.SqlSessionFactory;
  • public class UserDaoImpl implements UserDao {
  • private final SqlSessionFactory sqlSessionFactory;
  • public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
  • this.sqlSessionFactory = sqlSessionFactory;
  • }
  • public User selectUserById(Integer id) {
  • SqlSession sqlSession = this.sqlSessionFactory.openSession();
  • User user = sqlSession.selectOne("user.findUserById", 10);
  • return user;
  • }
  • }
  • // 测试类
  • import com.coderap.mybatis.dao.UserDaoImpl;
  • import com.coderap.mybatis.pojo.User;
  • import org.apache.ibatis.io.Resources;
  • import org.apache.ibatis.session.SqlSessionFactory;
  • import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  • import org.junit.Before;
  • import org.junit.Test;
  • import java.io.IOException;
  • import java.io.InputStream;
  • public class MybatisDaoTest {
  • public SqlSessionFactory sqlSessionFactory;
  • @Before
  • public void before() throws IOException {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • }
  • @Test
  • public void testDao() {
  • User user = new UserDaoImpl(this.sqlSessionFactory).selectUserById(10);
  • System.out.println(user);
  • }
  • }

上面这种写法虽然可行,但会产生很多重复代码。

Mybatis为我们提供了Mapper动态代理的功能,允许我们只编写Dao接口,就可以通过接口动态生成实现类并进行查询,需要注意的是,这种情况下,Dao接口的编写需要遵循以下的规范:

  1. Mapper.xml文件中的namespace与mapper接口的类路径相同;
  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同;
  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个SQL的parameterType的类型相同;
  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个SQL的resultType的类型相同。

根据以上规范,我们有以下的Dao实现:

  • package com.coderap.mybatis.mapper;
  • import com.coderap.mybatis.pojo.User;
  • public interface UserMapper {
  • public User findUserById(Integer id);
  • }

并且对User.xml进行修改,将其中的namespace修改为上述类的全限定名:

  • <?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.coderap.mybatis.mapper.UserMapper">
  • <!-- 通过ID查询一个用户 -->
  • <select id="findUserById" parameterType="Integer" resultType="com.coderap.mybatis.pojo.User">
  • SELECT * FROM user WHERE id = #{value}
  • </select>
  • </mapper>

这样一来,就可以直接使用Dao接口动态生成实现类进行查询操作了:

  • @Test
  • public void testMapper() throws Exception {
  • // 加载核心配置文件
  • String path = "sqlMapConfig.xml";
  • InputStream resourceAsStream = Resources.getResourceAsStream(path);
  • // 创建SqlSessionFactory
  • SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • // 创建SqlSession
  • SqlSession sqlSession = sqlSessionFactory.openSession();
  • // 获取Mapper
  • UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  • // 查询数据
  • User user = mapper.findUserById(10);
  • System.out.println(user);
  • }

通过SqlSessiongetMapper()方法,将Dao接口类型传入,Mybatis就会为我们动态生成实现类,直接使用实现类查询即可,运行结果如下:

  • DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?
  • DEBUG [main] - ==> Parameters: 10(Integer)
  • DEBUG [main] - <== Total: 1
  • User [id=10, username=张三, gender=1, birthday=Thu Jul 10 00:00:00 CST 2014, address=北京市]
  • Process finished with exit code 0

动态代理对象调用sqlSession.selectOne()sqlSession.selectList()是根据Mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。

Mybatis官方推荐使用Mapper代理方法开发Mapper接口,程序员不用编写Mapper接口实现类,使用Mapper代理方法时,输入参数可以使用POJO包装对象或Map对象,保证Dao的通用性。

6. sqlMapConfig.xml文件

在上面的例子中,我们已经使用了sqlMapConfig.xml文件的environments和mappers标签,接下来我们介绍剩余的几个常用标签。

6.1. properties标签引入外部属性

properties标签可以用于读取外部的properties文件,将其中的属性引入到该xml文件中使用,如我们定义了数据库连接信息的jdbc.properties文件:

  • jdbc.driver=com.mysql.jdbc.Driver
  • jdbc.url=jdbc:mysql://localhost:3306/javaeetest?characterEncoding=utf-8
  • jdbc.username=root
  • jdbc.password=12345678

就可以通过properties标签将其引入并使用:

  • <?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="jdbc.properties" />
  • <!-- 和Spring整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <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>
  • </environments>
  • <!-- Mapper文件的位置 -->
  • <mappers>
  • <mapper resource="./User.xml" />
  • </mappers>
  • </configuration>

MyBatis 将按照下面的顺序来加载属性:

  • properties元素体内定义的属性首先被读取。
  • 然后会读取properties元素中resourceurl加载的属性,它会覆盖已读取的同名属性。

6.2. typeAliases标签自定义别名

我们在编写Mapper.xml文件时,经常会用到POJO的全限定类名,如果用到的地方太多就会相当繁琐,所以Mybatis提供了typeAliases标签允许我们定义别名来简化书写。比如我们的User.xml文件中有以下的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.coderap.mybatis.mapper.UserMapper">
  • <!-- 通过ID查询一个用户 -->
  • <select id="findUserById" parameterType="Integer" resultType="com.coderap.mybatis.pojo.User">
  • SELECT * FROM user WHERE id = #{value}
  • </select>
  • </mapper>

其中,resultType返回类型就编写了User类的全限定类名。我们可以在sqlMapConfig.xml文件中定义别名:

  • <?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="jdbc.properties" />
  • <!-- 别名 -->
  • <typeAliases>
  • <typeAlias type="com.coderap.mybatis.pojo.User" alias="User" />
  • </typeAliases>
  • <!-- 和Spring整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <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>
  • </environments>
  • <!-- Mapper文件的位置 -->
  • <mappers>
  • <mapper resource="./User.xml" />
  • </mappers>
  • </configuration>

此时我们在User.xml文件中就可以直接将全限定类名写为User即可:

  • <?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.coderap.mybatis.mapper.UserMapper">
  • <!-- 通过ID查询一个用户 -->
  • <select id="findUserById" parameterType="Integer" resultType="User">
  • SELECT * FROM user WHERE id = #{value}
  • </select>
  • </mapper>

同时,typeAliases标签还提供了package标签允许我们传入一个包名,它会自动将该包下的POJO类名作为别名,在Mapper.xml文件中直接使用类名即可:

  • <?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="jdbc.properties" />
  • <!-- 别名 -->
  • <typeAliases>
  • <!-- 可写多个 -->
  • <package name="com.coderap.mybatis.pojo" />
  • <package name="com.coderap.mybatis.model" />
  • </typeAliases>
  • <!-- 和Spring整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <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>
  • </environments>
  • <!-- Mapper文件的位置 -->
  • <mappers>
  • <mapper resource="./User.xml" />
  • </mappers>
  • </configuration>

注:设置别名后,Mapper.xml中使用别名并不需要区分大小写。

6.3. mappers标签下的mapper和package

mapper标签用于引入Mapper.xml文件的位置,它还有一种常用的引入方式,使用classs属性方式:

当我们使用了Mapper动态代理生成Dao实现类的方式时,可以使用Dao类来作为Mapper.xml文件的位置指定方式,但这种方式下,Mapper.xml文件需要和Dao接口文件同名且放在同一个包下:

  • <?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="jdbc.properties" />
  • <!-- 别名 -->
  • <typeAliases>
  • <typeAlias type="com.coderap.mybatis.pojo.User" alias="User" />
  • <package name="com.coderap.mybatis.pojo" />
  • </typeAliases>
  • <!-- 和Spring整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <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>
  • </environments>
  • <!-- Mapper文件的位置 -->
  • <mappers>
  • <mapper class="com.coderap.mybatis.mapper.UserMapper" />
  • </mappers>
  • </configuration>

这样一来,Mybatis会自动根据Dao接口类映射找到Mapper.xml文件。

mappers标签还提供了package标签允许我们引入某个包下的所有Mapper.xml文件,这种方式也要求Mapper.xml文件名Dao接口文件名保持一致同时放在同一个包下:

  • <?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="jdbc.properties" />
  • <!-- 别名 -->
  • <typeAliases>
  • <typeAlias type="com.coderap.mybatis.pojo.User" alias="User" />
  • <package name="com.coderap.mybatis.pojo" />
  • </typeAliases>
  • <!-- 和Spring整合后 environments配置将废除 -->
  • <environments default="development">
  • <environment id="development">
  • <!-- 使用jdbc事务管理 -->
  • <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>
  • </environments>
  • <!-- Mapper文件的位置 -->
  • <mappers>
  • <package name="com.coderap.mybatis.mapper" />
  • </mappers>
  • </configuration>

注:需要注意的是,在Idea工具中,Mapper.xml文件是放在resources目录下的,此时我们需要保证Mapper.xml文件在resources目录中的路径名与Dao接口文件的路径名一致。