简介
mybatis是一个比较流行的ORM框架,在很多应用里都有牵涉到。它本身提供对原生sql语句的支持,同时可以让使用者采用更加细粒度的方式来设置对象和数据的映射关系。本文通过一个简单的示例来熟悉mybatis工程的配置和api应用。
工程示例
这个示例采用mysql数据库,结合mybatis的xml配置文件进行设置。
创建数据库
首先,我们创建一个数据库表。详细的创建表的sql脚本如下:
CREATE TABLE user (
user_id int(10) unsigned NOT NULL AUTO_INCREMENT,
email_id varchar(45) NOT NULL,
password varchar(45) NOT NULL,
first_name varchar(45) NOT NULL,
last_name varchar(45) DEFAULT NULL,
PRIMARY KEY (user_id),
UNIQUE KEY Index_2_email_uniq (email_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建好这个表之后,我们将看到如下的表结构信息:
定义程序依赖
因为这里要使用mybatis库,这里主要引用了目前最新的3.4.2的版本。另外,程序需要访问mysql数据库,也引用了mysql-connector-java库。因为程序采用maven来管理依赖关系,定义的pom.xml文件内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yunzero</groupId> <artifactId>mybatisSample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>mybatisSample</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> </plugins> </build> </project>
定义java实体对象
package com.yunzero.mybatisSample.domain;
public class User {
private Integer userId;
private String emailId;
private String password;
private String firstName;
private String lastName;
@Override
public String toString() {
return "User [userId=" + userId + ", emailId=" + emailId
+ ", password=" + password + ", firstName=" + firstName
+ ", lastName=" + lastName + "]";
}
// getter, setter methods
}
这里定义了对应的java实体类,也有类似的用户id, emailid等属性。 在定义好数据库表结构以及对应的实体类之后,剩下的就是该考虑怎么配置数据源以及实现它们之间的映射了。
定义mapper config文件
在访问数据库的时候,需要定义数据源相关的信息,创建的相关mapper-config.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.yunzero.mybatisSample.domain.User" alias="User"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/yunzero/mybatisSample/mappers/UserMapper.xml"/> </mappers> </configuration>
我们针对文件里面对应的配置项一一看过来。
properties
properties属性用来定义配置文件里读取属性配置文件的地方。在本示例里引用了jdbc.properties文件。这样在后面的dataSource部分定义数据库相关信息的时候,不用硬编码在xml文件里。而对应的jdbc.properties文件内容如下:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=password
采用这种配置方式的好处就是将一些牵涉到具体配置以及容易变更的内容放到外部文件,这样在牵涉到部署时可以是的修改更灵活。
TypeAliases
在上面的内容里,我们有一部分这样的内容:<typeAlias type="com.yunzero.mybatisSample.domain.User" alias="User"/>
它主要是给我们定义的实体类建立一个别名。这样,在后面定义具体映射的文件里,可以不用那么繁琐。除了上述的这种配置方式以外,还有一种就是直接引用整个包名的,比如:
<typeAliases>
<package name="com.mybatis3.domain" />
</typeAliases>
针对这种方式,相当于引入了整个包里所有的实体类,我们可以在映射文件里直接使用定义的类名而不用引用完整的包名。
environments
mybatis里有一个考虑比较全的地方就是引入了environments的配置项,因为在实际项目中,可能会针对不同的环境有不同的配置信息,像开发,测试,集成,产品等环境。所以,我们可以根据需要在这里配置若干个环境的信息。当具体部署到某个环境的时候,直接设置默认的环境信息就可以了。像本示例中,设置的default默认值就是development环境。
在具体的environment配置项里头,有几个配置项。TransactionManager主要用来设定事物管理的类型,它里面可以设置的值可以是JDBC或者MANAGED两种方式。当设置成JDBC的时候,它表示由应用程序来具体实现管理事物。比如说要在程序逻辑里描述什么时候commit以及什么时候rollback等。
而如果设置成MANAGED这种方式的话,则表示由具体的应用服务器来负责管理事物。像JBoss, WebLogic, GlassFish等服务器就可以通过EJB来管理应用的事物。
至于dataSource的配置主要就是设定访问的数据库类型,数据库链接,访问的用户名和密码等信息。dataSource里有一个type属性,它主要有三种类型,分别是UNPOOLED, POOLED和JNDI。其中UNPOOLED是在用户每次发送一个请求的时候创建一个连接,因为连接是临时创建的,所以它并不适合在有一定性能要求的情况下使用。POOLED则表示dataSource创建一个连接池,每次接收到请求的时候就从连接池里取一个连接,用完之后再将连接放回去。而JNDI则通过JNDI接口查询到服务器的配置,由应用服务器上面配置好连接池相关的信息。
mapper
在设定好上述的信息之后,具体描述它们如何映射的地方就是由mapper来指定的。除了上述的采用resource从指定的路径访问文件,还可以从其他的源来引用mapper配置。一些典型的如下:
<mapper url="file:///D:/mybatisdemo/app/mappers/TutorMapper.xml"/> <mapper class="com.mybatis3.mappers.TutorMapper"/> <package name="com.mybatis3.mappers"/>
其中,url可以用来指向一个完整的文件系统或者web url路径。而class用来指向一个mapper接口。package则指向一个包名,在这个包里包含有定义mapper的接口。基于mapper接口的定义方式是和基于xml配置方式不同一种,在后续的文章里会有进一步的讨论。
定义mapper接口
作为完整示例里的一部分,除了定义对象关系的映射,关于对这些数据操作方法的定义也是有必要的。所以,这里就有必要定义数据操作的接口:
package com.yunzero.mybatisSample.mappers;
import java.util.List;
import com.yunzero.mybatisSample.domain.User;
public interface UserMapper {
public void insertUser(User user);
public User getUserById(Integer userId);
public List<User> getAllUsers();
public void updateUser(User user);
public void deleteUser(Integer userId);
}
为什么要定义成接口呢?因为在对数据的操作上,我们希望它是独立于具体实现的,所以用接口能够保证。
定义mapper文件
综合来说,定义好了上述的内容之后,剩下的就是定义详细映射关系的地方了。这里我们采用mapper 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="com.yunzero.mybatisSample.mappers.UserMapper"> <select id="getUserById" parameterType="int" resultType="User"> SELECT user_id as userId, email_id as emailId , password, first_name as firstName, last_name as lastName FROM USER WHERE USER_ID = #{userId} </select> <!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. --> <resultMap type="User" id="UserResult"> <id property="userId" column="user_id"/> <result property="emailId" column="email_id"/> <result property="password" column="password"/> <result property="firstName" column="first_name"/> <result property="lastName" column="last_name"/> </resultMap> <select id="getAllUsers" resultMap="UserResult"> SELECT * FROM USER </select> <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="userId"> INSERT INTO USER(email_id, password, first_name, last_name) VALUES(#{emailId}, #{password}, #{firstName}, #{lastName}) </insert> <update id="updateUser" parameterType="User"> UPDATE USER SET PASSWORD= #{password}, FIRST_NAME = #{firstName}, LAST_NAME = #{lastName} WHERE USER_ID = #{userId} </update> <delete id="deleteUser" parameterType="int"> DELETE FROM USER WHERE USER_ID = #{userId} </delete> </mapper>
这个文件里定义的很多东西,也值得我们细细看过来。
mapper
mapper是配置文件里最外层的项,它有一个namespace的属性。这个属性必须和对应的mapper接口一致,这样mybatis就可以找到对应的接口方法来和里面的项映射。
resultMap
resultMap可以说是这个mapper文件里最核心的地方。因为它定义了对象里的元素和表中间具体列元素的对应关系。像它里面的property就是对应类里的成员,column就是表里面的列。有了这个映射关系,在后面的一些CRUD操作的时候,可以引用它作为参数来指导具体的映射。
select
在上面的配置里,我们有两个地方使用了select配置语句。一个是getUserById,一个是getAllUsers。值得注意的是,这里的两个值就是两个select语句里定义的id,同时,它们也是对应mapper接口里对应的方法。它们必须要保持一致。就是因为它们这里的一致才能保证mybatis找到对应的接口方法和具体的配置项关联起来。
这两个方法里也有不一样的地方,像getUserById里定义了parameterType为int,它对应接口里查询的时候需要提供的int类型的参数。同时,它对应的返回值有resultType=com.yunzero.mybatisSample.domain.User。它设定了返回值的类型。这里值得注意的地方就是,因为它是直接指定返回值是那个类,所以在它的查询语句里
SELECT user_id as userId, email_id as emailId , password, first_name as firstName, last_name as lastName FROM USER WHERE USER_ID = #{userId}
它实际上返回的表里头每个列的字段名必须和定义的User类里的字段一致。否则会导致取不出对应值的问题。
当然,还有另外一种映射方式,就是在select里面指定resultMap=UserResult,这个时候,我们返回的结果就是期望的了。在实际查询中,我们可以采用设置resultType的方式,也可以采用resultMap的方式。但是不能同时使用两者。
insert
insert方法的描述相对来说并不复杂,它这里有几个地方值得注意。一个是因为它是需要传入一个对应的User对象,所以要指定它的parameterType。另外,由于在前面定义数据库表的时候,对应的user_id项是由数据库生成并自增的。在这种情况下,可以说我们不需要在传入的对象里指定user_id。所以这里需要配置useGeneratedKeys="true" keyProperty="userId" 。其中keyProperty用来设定类里头的哪个成员作为它的key。当然,如果我们插入元素没有设定id自增的话,我们可以在插入元素的语句里把对应的部分加上,也不需要设定useGeneratedKeys这些。
除了上面这部分。我们也看到insert语句里用到很多#{}包装的参数。它就表示类里头的成员变量。在前面的select语句里也有出现过。只要保证它和类成员的定义是一致的就可以了。
update
关于update的部分和前面insert很类似,也就是设定好对应的方法名,然后对应好参数就可以了。其他就和写sql语句一样。
delete
delete语句的过程也非常类似。一般来说,我们只要保证传入的参数正确就好说了。
这样,有了上述的这些基本配置,我们mybatis的基本配置工作就弄好了。当然,上面还有很多没有讨论到的地方,比如查询里头如果有更复杂的连接以及级联查询,该怎么实现呢?另外,如果查询等操作需要多个参数,该怎么处理呢?这些内容我们将在后面的文章中进一步讨论。
集成起来
在配置好上面的内容之后,就该考虑怎么利用mybatis的api来进行具体的数据库操作了。mybatis里头,我们需要首先定义好SqlSessionFactory,通过这个对象再来根据每个具体的请求创建SqlSession。所以我们需要创建一个全局唯一的SqlSessionFactory,因为只要有一个这样的对象就可以创建多个SqlSession了。这部分的代码实现如下:
package com.yunzero.mybatisSample.util;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisUtil {
private static SqlSessionFactory factory;
private MyBatisUtil() { }
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
factory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSessionFactory getSqlSessionFactory() {
return factory;
}
}
详细的实现采用了单例模式,通过读取mybatis-config.xml文件来创建factory对象。
创建好factory对象之后,剩下的就是来操作具体的数据库了。我们一般封装建立一个对象来将所有的操作方法放在里面:
package com.yunzero.mybatisSample.service;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.yunzero.mybatisSample.domain.User;
import com.yunzero.mybatisSample.mappers.UserMapper;
import com.yunzero.mybatisSample.util.MyBatisUtil;
public class UserService {
public void insertUser(User user) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insertUser(user);
sqlSession.commit();
} finally {
sqlSession.close();
}
}
public User getUserById(Integer userId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.getUserById(userId);
} finally {
sqlSession.close();
}
}
public List<User> getAllUsers() {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.getAllUsers();
} finally {
sqlSession.close();
}
}
public void updateUser(User user) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateUser(user);
sqlSession.commit();
} finally {
sqlSession.close();
}
}
public void deleteUser(Integer userId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(userId);
sqlSession.commit();
} finally {
sqlSession.close();
}
}
}
在上述的代码里,我们可以看到它遵循一个基本的套路,就是首先通过factory的openSession方法来获取SqlSession对象。然后在针对不同操作的时候,通过getMapper引入前面定义的Mapper接口来进行操作。在操作完sqlSession之后,我们还需要关闭它。
还有一个地方就是,在insertUser, updateUser和deleteUser的操作中,因为要修改数据库,所以采用事物提交的方式防止出现数据的不一致。结合前面配置项里设定的TransactionManager是JDBC,所以这里需要手工的来管理事物的提交和回滚。
有了上述的基础,我们使用这些类的示例代码如下:
package com.yunzero.mybatisSample;
import java.util.List;
import com.yunzero.mybatisSample.domain.User;
import com.yunzero.mybatisSample.service.UserService;
public class App {
public static void main(String[] args) {
UserService userService = new UserService();
User user = new User();
user.setEmailId("test_email_" + System.currentTimeMillis() + "@gmail.com");
user.setPassword("secret");
user.setFirstName("TestFirstName");
user.setLastName("TestLastName");
// insert user
userService.insertUser(user);
System.out.println(user);
// get user by id
user = userService.getUserById(1);
System.out.println(user);
// list all users
List<User> list = userService.getAllUsers();
System.out.println(list.size());
// update user
user.setLastName("newLastName");
userService.updateUser(user);
user = userService.getUserById(1);
System.out.println(user);
// delete user
userService.deleteUser(1);
list = userService.getAllUsers();
System.out.println(list.size());
}
}
这样,一个完整的mybatis的示例就可以运行起来了。代码的详细实现可以看附件里所带的完整示例。
总结
在这篇文章里,我们主要总结了从头到尾创建一个简单的mybatis示例的过程,并介绍了采用xml配置的方式里的细节。从前面的体验来看,如果只是使用原生的mybatis api来做开发的话,它可以在一定程度上减少代码的冗余,但是总体的工作量还是稍微有点大。在后续的文章里我们会讨论mybatis的其他功能以及采用annotation配置的方式。
参考材料
https://www.javacodegeeks.com/2012/11/mybatis-tutorial-crud-operations-and-mapping-relationships-part-1.html