MyBatis实战之初步
关于MyBatis与Hibernate及其JDBC的比较,大家可以参考我的这篇文章:MyBatis+Hibernate+JDBC对比分析
如果觉得这个还不够系统全面,可以自行Google或者百度。
用了MyBatis有两年了,后来觉得不好用有一定的局限性换成了MyBatis-Plus,关于MyBatis-Plus实战系列,可以参考这个链接:https://www.cnblogs.com/youcong/category/1213059.html
另外话说回来了,为什么我又要回到MyBatis?
原因有这么几个方面?
第一、MyBatis-Plus是MyBatis的升级版,MyBatis有的,MyBatis-Plus都有,MyBatis没有的,MyBatis-Plus也有,从这个角度来看,MyBatis-Plus是从MyBatis中衍生出来的,那么它们的源码可以说有一大半是一致的,只是有少部分不同,所以如果以后MyBatis-Plus不能满足我的需求,我可以自行改造,虽说码云或者Github上有不少现成的轮子可以用,但是我个人觉得,如果进入一家比较大的公司,不仅仅要知道如何投机取巧(善用现有的轮子,同时也要具备制造轮子的能力,你可以理解为深入了解源码,并熟悉其中的设计模式);
第二、如第一条所说的那样,我看过很多开源项目,有的理解透彻MyBatis,知道MyBatis生成的代码有很多局限性,就例如它的逆向工程可以避免重复编写CRUD之类的代码,但是生成的XML太繁琐,于是就有了MyBatis-Plus,但是MyBatis-Plus也并不是没有缺点的,比如它只能针对单表,而不能多表连接,也正是因为这个局限性,Jeesite的创造者才决定自行改造MyBatis,让其符合自身的需要,关于Jeesite简化MyBatis的博文,大家可以参考这个链接:https://my.oschina.net/thinkgem/blog/1503611;
第三、目前主流还是MyBatis,互联网项目大多都是MyBatis,有的公司也有自己封装的ORM框架,但是本质上基本要么是对Hibernate进行改造,要么就是对MyBatis改造,下面我贴一下关于MyBatis的图给大家看看:
从这张图,大家可以知道MyBatis和MyBatis-Plus在开源项目都彼此占用;
有人说,现在博客上有很多关于MyBatis的,你写这个是不是有点多余的,我对此的回答是,不多余。就好比读书,每个人读一本书一遍或者两遍及其以上,感触肯定是不一样的。很多东西当初的时候不知道是什么意思,回过头来,你会发现,你可以发现一些不一样的东西,这个东西,你可以理解为举一反三或是融会贯通。
下面进入正题,关于MyBatis的学习,我当初是参考孤傲苍狼的博客。今天在写这篇文章时,我查了官网,也搜索相关的中文教程,在w3cschool发现了关于MyBatis,但是我觉得写的不好,对于初学者和进阶者而言,简直就是乱七八糟,我知道也许这样评价会引起一些人讽刺,但是这是事实,大家可以自己看,我觉得初学者或者进阶者,特别是进阶者可以回过头再看看官网。
准备环境:Window10/8/7+JDK8/7/6或以上+MySQL
项目结构如下:
SQL脚本:
REATE TABLE `user` (
`user_id` int(8) NOT NULL AUTO_INCREMENT COMMENT '用户主键',
`login_code` varchar(20) NOT NULL COMMENT '用户编码(登录账户) 手机号 邮箱号',
`user_name` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(40) NOT NULL COMMENT '密码',
`sex` int(2) NOT NULL COMMENT '性别',
`identity_card` varchar(20) DEFAULT NULL COMMENT '身份证',
`create_time` datetime NOT NULL COMMENT '创建时间',
`create_by` varchar(10) NOT NULL COMMENT '创建人',
`update_time` datetime NOT NULL COMMENT '更新时间',
`update_by` varchar(10) NOT NULL COMMENT '更新人',
`status` int(2) NOT NULL DEFAULT '0' COMMENT '状态:0注册新用户 1邮件认证用户 2管理员 3黑名单',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
一、导入依赖
<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>cn.youcong.mybatis</groupId> <artifactId>mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
二、编写实体类
package com.blog.entity;
import java.io.Serializable;
import java.util.Date;
import java.io.Serializable;
public class User{
private static final long serialVersionUID = 1L;
/**
* 用户主键
*/
private Integer userId;
/**
* 用户编码(登录账户) 手机号 邮箱号
*/
private String loginCode;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private Integer sex;
/**
* 身份证
*/
private String identityCard;
/**
* 创建时间
*/
private Date createTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 更新人
*/
private String updateBy;
/**
* 状态:0注册新用户 1邮件认证用户 2管理员 3黑名单
*/
private Integer status;
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getLoginCode() {
return loginCode;
}
public void setLoginCode(String loginCode) {
this.loginCode = loginCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getIdentityCard() {
return identityCard;
}
public void setIdentityCard(String identityCard) {
this.identityCard = identityCard;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
三、编写数据访问层及其对应的XML文件
package com.blog.dao;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.blog.entity.User;
public interface UserDao {
@Select("select * from `user` where user_id=#{userId}")
@Results(@Result(property="userName",column="user_name"))
User selectOne(int userId);
}
@Select是MyBatis的注解写法,当字段名和属性名一致时,可以直接@Select不需要@Results,@Results的作用就是因为实体属性与字段不一致,为了获取查询结果,必须要这样。
当然了你也可以不写注解,直接在对应的UserDao.xml写,如果是UserDao.xml里面写的话,就会变成这样
package com.blog.dao; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import com.blog.entity.User; public interface UserDao { User selectOne(int userId); }
需要在UserDao.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.blog.dao.UserDao"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.blog.entity.User"> <id column="user_id" property="userId" /> <result column="login_code" property="loginCode" /> <result column="user_name" property="userName" /> <result column="password" property="password" /> <result column="sex" property="sex" /> <result column="identity_card" property="identityCard" /> <result column="create_time" property="createTime" /> <result column="create_by" property="createBy" /> <result column="update_time" property="updateTime" /> <result column="update_by" property="updateBy" /> <result column="status" property="status" /> </resultMap> <!-- 通用查询结果列 -->
<sql id="Base_Column_List">
user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status
</sql>
<select id="selectOne" resultMap="BaseResultMap"> select * from `user` where user_id=#{userId} </select> </mapper>
除了<select>标签之外,还有<insert>、<update>、<delete>,它们的作用分别是查、增、更、删。当然了,还有这里面可以<sql>,这个<sql>的作用就是为了复用重复列,省的重复编写代码冗余。如果要引用,写成这样既可
<!-- 通用查询结果列 --> <sql id="Base_Column_List"> user_id, login_code, user_name, password, sex, identity_card, create_time, create_by, update_time, update_by, status </sql> <select id="selectOne" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from `user` where user_id=#{userId} </select>
MyBatis常用的两种写法,XML方式和注解方式,但是无论你采用哪种,对应的XML文件必须要存在。
其实<resultMap id="BaseResultMap" type="com.blog.entity.User"> 可以变成 <resultMap id="BaseResultMap" type="User">
前提必须要在mybatis-config.xml配置
<typeAliases> <typeAlias type="com.blog.entity.User" alias="User"/> </typeAliases>
但是有人觉得,如果我的类有很多,岂不是要配置很多,太麻烦了,别怕,MyBatis已经为你想到了
<typeAliases> <package name="com.blog.entity"/> </typeAliases>
这样配置就解决了重复配置的麻烦
其实除了这样,如果你想标新立异点,还可以再实体最上面加上@Alias注解
这就是MyBatis的别名三种方式/策略。
四、编写mybatis-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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/blog_test"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/mapping/UserDao.xml"/> </mappers> </configuration>
mybatis-config.xml这个配置没多大用,坦白说。
因为后期与Spring整合由Spring来管理数据库连接池对象。
不过还是要稍微讲讲,以让读者达到开卷有益的目的或者是让没有学过的或者已经学过但是不知道的涨涨见识。
<environment id="development">表示我们默认使用development配置环境
<transactionManager type="JDBC"/> 采用JDBC的事务管理模式
<dataSource type="POOLED"> 数据库连接信息
<mappers> 映射器 通常一般映射XML文件
五、编写工具类
package com.blog.config; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class SqlSessionFactoryUtils { private static SqlSessionFactory sqlSessionFactory = null; public static SqlSessionFactory getSqlSessionFactory() { InputStream is = null; if(sqlSessionFactory==null) { String resource = "mybatis/mybatis-config.xml"; try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource)); return sqlSessionFactory; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return sqlSessionFactory; } }
说到上述代码,顺便谈谈MyBatis的核心组件。它的核心组件主要有这么几个,如下所示:
SqlSessionFactoryBuilder:它会根据配置信息或者代码来生成SqlSessionFactory;
SqlSessionFactory:依赖工厂生成SqlSession;
SqlSession:是一个即可发送SQL执行返回结果,也可以获取Mapper接口;
Sql Mapper:它是MyBatis的新设计组件,它是由一个Java接口和XML文件(或注解)构建,需要给出对应的SQL和映射规则。它负责发送SQL去执行并返回结果。
用一张图来表示它们之间的关系,如图:
它们的生命周期在此稍微说一下:
(1)SqlSessionFacotoryBuilder
SqlSessionFacotoryBuilder是利用XML和Java代码获得资源来构建SqlSessionFactory的,通过它可以构建多个SqlSessionFactory,它的作用就是一个构建器,一旦我们构建了SqlSessionFactory,它的作用就完结,它就失去存在的意义。这时我们应该毫不犹豫的废弃它,将它回收。所以它的生命周期只存在方法局部,它的作用就是生成SqlSessionFactory。
(2)SqlSessionFactory的作用是创建SqlSession,而SqlSession就是一个会话,相当于JDBC中的Connection对象。每次应用程序需要访问数据库,我们就要通过SqlSessionFactory创建SqlSession,所以SqlSessionFactory应该在MyBatis应用的整个生命周期中。而如果我们多次创建同一个数据库的SqlSessionFactory,则每次创建SqlSessionFactory会打开更多的数据库连接资源,那么连接资源就很快会被耗尽。因此SqlSessionFactory的责任是唯一的,它的责任就是创建SqlSession,所以我们果断采用单例模式。如果采用多例,那么它对数据库连接的消耗是很大的,不利于我们统一管理。所以正确的做法是使得每个数据库只对应一个SqlSessionFactory,管理好数据库资源分配,避免过多的Connection被消耗。
(3)SqlSession
SqlSession是一个会话,相当于JDBC的一个Connection对象,它的生命周期应该是在请求数据库处理事务的过程中。它是一个线程不安全的对象,在涉及多线程的时候我们需要特别当心,操作数据库需要注意其隔离级别,数据库锁等高级特性。此外,每次创建的SqlSession都必须及时关闭它,它长期存在就会使数据库连接池的活动资源减少,对系统性能的影响很大。
(4)Mapper
Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果,或者执行SQL从而修改数据库的数据,因此它应该在一个SqlSession事务方法之内,是一个方法级别的东西。它就如同JDBC中的一条SQL语句的执行,它的最大范围和SqlSession是相同的。尽管我们想一直保存着Mapper,但是你会发现它很难控制,所以尽量在一个SqlSession事务的方法中使用它们,然后废弃掉。
MyBatis组件生命周期,如图:
六、测试
package mybatis; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import com.blog.config.SqlSessionFactoryUtils; import com.blog.dao.UserDao; import com.blog.entity.User; public class MyBatisTest { @Test public void testName() throws Exception { SqlSession sqlSession = null; sqlSession = SqlSessionFactoryUtils.getSqlSessionFactory().openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.selectOne(1); System.out.println(user.getUserName()); } }
小结:
通过这篇文章,你可以达到能够知道怎么使用MyBatis和了解熟悉它的相关执行原理和对应的生命周期,希望能给大家带来有益的启发和收获。
本文参考:
《深入浅出MyBatis技术原理和实战》
MyBatis官网:http://www.mybatis.org/mybatis-3/