前言
利用端午时间简单学习了MyBatis,现根据学习内容进行简单总结。
知识点
1. MyBatis框架简介
MyBatis是一个持久化框架,使用Java编写,封装了JDBC的很多实现细节,可以使开发者只关注SQL语句,而无需关注注册驱动/建立连接等繁杂的过程,封装过程使用ORM思想来实现。
ORM:Object Relational Mapping 对象关系映射,把数据库表和实体类的属性对应起来,使得可以通过操作实体类来实现数据库表
2. 基于XML和基于注解的开发实例
以User为实体类,构建简单的一个入门实例,通过MyBatis操作实体类完成对数据库user表的CRUD操作,之后分别会用基于XML和基于注解的方式完成。
2.1 建表
通过Navicat连接MySQL完成建表操作。
2.1.1 启动mysql服务【管理员模式打开cmd】
2.1.2 创建User表并插入数据
2.2 导入依赖
创建maven项目,导入mybatis/mysql-connection等依赖
<dependencies>
<!-- MyBatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- log4j依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.3 创建User实体类/IUserDao接口
2.3.1 根据数据库字段创建User实体类并定义属性和getter/setter方法
/**
* User实体类
*/
public class User implements Serializable {
private Long userId;
private String username;
private String password;
private Date birthday;
private String sex;
private String address;
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
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 Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2.3.1 创建User接口类,并根据查询所有用户功能创建抽象方法
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAllUsers(); //public abstract已省略
}
2.4 添加映射配置文件
配置文件主要是配置数据库连接信息以及为方便给类名起别名,配置映射文件的路径
<?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">
<!--MyBatis的主配置文件-->
<configuration>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源/连接池-->
<dataSource type="POOLED">
<!--配置连接数据库的4个基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="com/practice/dao/IUserDao.xml"/>
</mappers>
</configuration>
2.5 基于XML模式的映射文件及查询所有用户功能测试
基于XML模式主要是映射配置文件中mapper中会指定resource为映射文件的全限定路径,另外根据IUserDao接口的方法编写映射文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace锁定接口 -->
<mapper namespace="com.practice.dao.IUserDao">
<!-- id锁定方法,resultType为方法返回类型,另外还有parameterType表示方法参数类型 -->
<select id="findAllUsers" resultType="com.practice.domain.User">
select * from user
</select>
</mapper>
public class IUserTest {
private InputStream in;
private SqlSession session;
private IUserDao userDao;
/**
* 初始化操作
*/
@Before //每一个测试之前都需要初始化
public void init() throws IOException {
//1.读取映射配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.构建SqlSessionFactory工厂对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.构建SqlSession会话对象
session = factory.openSession();
//4.构建代理IUserDao对象
userDao = session.getMapper(IUserDao.class);
}
/**
* 测试查询所有用户
*/
@Test
public void testFindAllUsers(){
List<User> users = userDao.findAllUsers();
for(User user : users){
System.out.println(user);
}
}
@After //在每一个测试之后都需要释放资源
public void destroy() throws IOException {
//释放资源
session.close();
in.close();
}
}
测试结果即查询到所有的用户信息。
2.6 基于注解的接口及查询所有用户功能测试
基于注解主要是不使用映射文件,映射配置文件中的mapper使用class属性,值为接口的全限定路径。
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user") //通过此注解完成对数据库的操作。package+方法名对比xml中的namespace,接口方法对比xml中的id,注解中的sql对比xml中的sql,方法的参数和返回值对比xml中的parameterType和resultType
List<User> findAllUsers();
}
基于注解的方法测试同基于xml的方法测试,测试结果相同。后续其他CRUD功能都会分别以基于XML和基于注解进行说明。
2.7 一般开发中使用上述中的代理模式进行开发,但是也可以通过daoImpl实现,此处以基于xml方式+daoimpl进行简单说明一下
创建实现IUserDao的实现类IUserDaoImpl,并在方法中创建SqlSession。调用SqlSession的selectList方法,参数为namespace+id。SqlSession还支持insert/selectOne/delete/update方法, 参数也可以加Object对象(例如insert的参数为路径和user对象)
public class IUserDaoImpl implements IUserDao {
private SqlSessionFactory sessionFactory;
/**
* 通过构造函数保证SqlSessionFactory不为空
* @param sqlSessionFactory
*/
public IUserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sessionFactory = sqlSessionFactory;
}
public List<User> findAllUsers() {
//1.使用工厂创建SqlSession对象
SqlSession session = sessionFactory.openSession();
//2.使用session执行查询所有方法
List<User> users = session.selectList("com.practice.dao.IUserDao.findAllUsers");
//释放资源
session.close();
//3.返回查询结果
return users;
}
}
由于这种开发比较繁琐,此处只是简单说明。目前用到的基本都是基于xml的开发模式。
2.8 typeAliases与package的使用
在使用中可能会有多个实体类,为了xml中参数方便不再重复写全限定路径,可以中typeAiases标签在映射配置文件中进行设置。
如果实体类比较多的话,可以使用package标签。
另外,映射配置文件中mapper标签也支持package
3. MyBatis的基本操作
主要是记录xml和注解的表达,测试和上述测试类似,差别的地方在于调用接口的不同方法,不再赘述。xml和注解两种开发不可并存,否则运行会报错(即不能有注解之后还有xml映射文件)
3.1 根据ID查询用户
3.1.1 基于xml
<!-- 根据用户ID查询用户 -->
<select id="findById" parameterType="LONG" resultType="com.practice.domain.User">
select * from user
where userId=#{userId}
</select>
3.1.2 基于注解
@Select("select * from user where userId=#{userId}")
@ResultMap(value = {"userMap"})
User findByUserId(Long userId);
3.2 根据用户名模糊查询用户
3.2.1 基于XML
<!-- 根据用户名模糊查询用户 -->
<!-- preparedStatement对象 -->
<select id="findByName" parameterType="java.lang.String" resultType="com.practice.domain.User">
select * from user
where username like #{username}
</select>
<!-- statement对象 -->
<!-- <select id="findByName" parameterType="java.lang.String" resultType="com.practice.domain.User">-->
<!-- select * from user-->
<!-- where username like '%${value}%'-->
<!-- </select>-->
3.2.2 基于注解
@Select("select * from user where username like #{username}") //preparedStatement,较为常用
// @Select("select * from user where username like '%${value}%' ") //statement
@ResultMap("userMap")
List<User> findByUsername(String username);
3.3 增加用户
3.3.1 基于XML
<!-- 添加一个用户 -->
<insert id="saveUser" parameterType="USER">
<!-- 配置插入操作后,返回插入数据的ID -->
<selectKey keyProperty="userId" keyColumn="userId" resultType="LONG" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,password,birthday,sex,address)
values(#{username},#{password},#{birthday},#{sex},#{address})
</insert>
3.3.2 基于注解
@Insert("insert into user(username,password,birthday,sex,address) " +
"values(#{username},#{password},#{birthday},#{sex},#{address})")
void saveUser(User user);
3.4 更新用户
3.4.1 基于XML
<!-- 更新一个用户 -->
<update id="updateUser" parameterType="uSer">
update user set
username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address}
where userId=#{userId}
</update>
3.4.2 基于注解
@Update("update user set username=#{username},password=#{password}," +
"birthday=#{birthday},sex=#{sex},address=#{address} where userId=#{userId}")
void updateUser(User user);
3.5 删除用户
3.5.1 基于XML
<!-- 删除一个用户 #{占位符是可以随意定义的}-->
<delete id="deleteUser" parameterType="java.lang.Long">
delete from user
where userId=#{userId}
</delete>
3.5.2 基于注解
@Delete("delete from user where userId=#{userId}")
void deleteUser(Long userId);
3.6 resultMap的使用
一般开发中会遇到实体类的属性名和数据库字段名不一致的情况,可以通过ResultMap进行映射匹配,例如数据库中user_id对应实体类中的userId,分别基于xml和基于注解进行属性映射
3.6.1 基于XML
<!-- 配置 查询结果的列名和实体类属性名的对应关系 -->
<resultMap id="userMap" type="com.practice.domain.User">
<!-- 主键字段的对应 -->
<id property="user_id" column="user_id"></id>
<!-- 非主键字段的对应 -->
<result property="userName" column="username"></result>
<result property="password" column="password"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</resultMap>
<!-- 使用提前配置数据库字段与实体类属性名的对应关系后,此处resultType需要改成userMap(即导入对应关系配置)
parameterMap同理,如果参数为实体类的话需要使用parameterMap="userMap"-->
<select id="findAllUsers" resultMap="userMap">
select * from user
</select>
3.6.2 基于注解
@Select("select * from user")
@Results(id="userMap", //声明id之后其他使用该映射规则的直接使用resultMap,然后写入这个id即可
value = {@Result(id = true,column = "user_id",property = "userId"),
@Result(column = "username",property = "username"), //非主键属性,id默认为false
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address")}
)
List<User> findAllUsers();
4. MyBatis的多表查询操作
4.1 多对一:例如多个账户可以隶属于同一个用户
在MyBatis中多对一可以看作一对一,对于一个账户而言,只有一个用户。实现功能:查询所有的账户并返回账户对应的用户信息,通过关联属性
4.1.1 在账户Account实体类中建立关联属性User
4.1.2 基于xml实现关联
<resultMap id="accountUserMap" type="com.practice.domain.Account">
<id property="accountId" column="accountId"></id>
<result property="userId" column="userId"></result>
<result property="money" column="money"/>
<!-- 一对一的关系映射,配置封装user的内容 -->
<association property="user" column="userId" javaType="com.practice.domain.User">
<id property="userId" column="userId"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
4.1 基于注解实现关联
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id=true,column = "accountId",property = "accountId"),
@Result(column = "userId",property = "userId"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "userId",
one = @One(select="com.practice.dao.IUserDao.findByUserId"))
})
List<Account> findAllAccounts();
4.2 一对多:例如一个用户可以拥有多个账户
实现功能:查询所有用户并返回用户对应的所有账户信息,通过集合属性
4.2.1 User实体类中创建集合属性Account
4.2.2 基于xml实现集合
<!-- 定义User的resultMap -->
<resultMap id="userMap" type="com.practice.domain.User">
<id property="userId" column="userId"></id>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="roles" ofType="role">
<id property="roleId" column="roleId"></id>
<result property="rolename" column="rolename"></result>
</collection>
</resultMap>
4.2.3 基于注解实现集合
@Select("select * from user")
@Results(id="userMap",
value = {@Result(id = true,column = "userId",property = "userId"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address"),
@Result(property = "accounts",column = "userId",
many = @Many(select="com.practice.dao.IAccountDao.findByUserId"))}
)
List<User> findAllUsers();
查询的时候直接调用User对象的getAccounts方法即可。
4.3 多对多:可以划分为两个一对多关系,分别使用集合属性完成,此处不再赘述
5. MyBatis中的连接池
MyBatis支持三种数据库连接方式,POOLED/UNPOOLED/JNDI
5.1 POOLED
采用传统的java.sql.DataSource规范中的连接池,MyBatis中有针对规范的实现
5.2 UNPOOLED
采用传统的获取连接的方式,虽然也实现java.sql.DataSource接口,但是并没有使用池的思想,只是单纯建立数据库连接
5.3 JNDI
采用服务器提供的JNDI技术来获取DataSource对象,不同的服务器所能拿到的DataSource不同。如果不是web/maven的war工程是不能使用的,tomcat服务器中采用连接池为dbcp连接池
6. MyBatis中的加载
6.1 立即加载
不管用不用,只要一调用方法,马上发起查询。一般一对一使用立即加载。
6.1.1 基于XML的立即加载
<resultMap id="accountUserMap" type="com.practice.domain.Account">
<id property="accountId" column="accountId"></id>
<result property="userId" column="userId"></result>
<result property="money" column="money"/>
<!-- select属性指定的内容:查询用户的唯一标识
column属性指定的内容:用户根据id查询时所需要的参数值-->
<association property="user" column="userId" javaType="user"
select="com.practice.dao.IUserDao.findById"></association>
</resultMap>
<select id="findAllAccounts" resultMap="accountUserMap">
select * from account
</select>
6.1.2 基于注解的立即加载,FetchType为EAGER
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id=true,column = "accountId",property = "accountId"),
@Result(column = "userId",property = "userId"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "userId",
one = @One(select="com.practice.dao.IUserDao.findByUserId",fetchType = FetchType.EAGER))
})
List<Account> findAllAccounts();
6.2 延迟加载
在真正使用数据时才发起查询,不用的时候不查询,按需加载(懒加载)。一般一对多使用延迟加载
无论是xml还是注解,都需要在映射配置文件中设置开启延迟加载。
6.2.1 基于XML的延迟加载
<!-- 定义User的resultMap -->
<resultMap id="userMap" type="com.practice.domain.User">
<id property="userId" column="userId"></id>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="account" ofType="account"
select="com.practice.dao.IAccountDao.findAccountByUserId" column="userId"></collection>
</resultMap>
<select id="findAllUsers" resultMap="userMap">
select * from user
</select>
<!-- 查询用户直接通过这个select查,如果需要查询账户,则使用userId作为参数去IAccountDao的映射文件中去找findAccountByUserId去查 -->
6.2.2 基于注解的延迟加载,FetchType为LAZY
@Select("select * from user")
@Results(id="userMap",
value = {@Result(id = true,column = "userId",property = "userId"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address"),
@Result(property = "accounts",column = "userId",
many = @Many(select="com.practice.dao.IAccountDao.findByUserId",fetchType = FetchType.LAZY))}
)
List<User> findAllUsers();
7. MyBatis中的缓存
7.1 一级缓存
MyBatis中SqlSession对象的缓存。当执行查询之后,查询的结果会同时存入到SqlSession提供的一块区域中,区域的结构是一个Map。当再次查询相同数据时MyBatis会先去SqlSession中查询是否有,有的话会直接拿来用。当SqlSession消失后这个缓存Map也会随之消失。
7.2 二级缓存
MyBatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
7.2.1 基于XML开发中二级缓存的开启方式
1. 在映射配置文件配置让MyBatis支持二级缓存
2. 在映射文件中配置
3. 在具体的方法中配置
7.2.2 基于注解开发中二级缓存的开启方式
1. 在映射配置文件中配置
2. 在接口类中使用注解进行配置
总结
千里之行始于足下,积少成多,认真踏实,好好学习,努力进步。