文章目录
Mybatis 注解开发
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。
这个是一般来说,一个 Mybatis 项目的目录结构:
这个里面有两个 xml 文件,一个是主配置文件 sqlMapConfig.xml,另一个 IUserDao.xml 映射配置文件。
1. Mybatis 注解开发的环境搭建
-
不适用骨架创建一个maven项目
-
在 pom.xml 文件中导入以下的依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>s
-
在 java 文件夹下创建 domain 包,创建好实体类
package com.mybatis.domain; import java.io.Serializable; import java.util.Date; public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; 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 getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", address='" + address + '\'' + ", sex='" + sex + '\'' + ", birthday=" + birthday + '}'; } }
-
在 java 文件夹在创建好 dao 类的包,并创建好 dao 接口类:
package com.mybatis.dao; import com.mybatis.domain.User; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 在 Mybatis 中针对 CRUD 一共有4个注解 * @Select @Insert @Delete @Update */ public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") List<User> findAll(); }
-
接着在 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> <!-- 引入外部配置文件--> <properties resource="jdbcConfig.properties"></properties> <!-- 配置别名--> <typeAliases> <package name="com.mybatis.domain"></package> </typeAliases> <!-- 配置环境变量--> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <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> <!-- 指定带有注解的dao接口所在位置--> <mappers> <package name="com.mybatis.dao"/> </mappers> </configuration>
-
然后,为了使数据库连接池能够正常的使用,需要创建一个存放有连接配置的配置文件:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/day23?serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=root
-
至此,环境已经搭建完毕。
-
以下是目录结构:
-
在测试包中创建测试类来测试我们的项目:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testFindAll() { List<User> all = iUserDao.findAll(); for (User user: all) { System.out.println(user); } } }
-
最后查询的结果为:
2019-09-09 20:33:33,725 96 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection 2019-09-09 20:33:34,705 1076 [ main] DEBUG source.pooled.PooledDataSource - Created connection 1217467887. 2019-09-09 20:33:34,708 1079 [ main] DEBUG m.mybatis.dao.IUserDao.findAll - ==> Preparing: select * from user 2019-09-09 20:33:34,732 1103 [ main] DEBUG m.mybatis.dao.IUserDao.findAll - ==> Parameters: 2019-09-09 20:33:34,760 1131 [ main] DEBUG m.mybatis.dao.IUserDao.findAll - <== Total: 6 User{id=41, username='老王', address='北京', sex='男', birthday=Tue Feb 27 17:47:08 CST 2018} User{id=42, username='小二王', address='北京金燕龙', sex='女', birthday=Fri Mar 02 15:09:37 CST 2018} User{id=43, username='update user clear cache', address='江苏南通海安', sex='男', birthday=Sun Mar 04 11:34:34 CST 2018} User{id=45, username='传智播客', address='北京金燕龙', sex='男', birthday=Sun Mar 04 12:04:06 CST 2018} User{id=46, username='老王', address='北京', sex='女', birthday=Wed Mar 07 17:37:26 CST 2018} User{id=48, username='小马宝莉', address='北京修正', sex='女', birthday=Thu Mar 08 11:44:00 CST 2018} 2019-09-09 20:33:34,762 1133 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@489115ef] 2019-09-09 20:33:34,762 1133 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 1217467887 to pool.
2. Mybatis 常用的基础注解说明
- @Insert:实现新增
- @Update:实现更新
- @Delete:实现删除
- @Select:实现查询
- @Result:实现结果集的封装
- @Results:可以与 @Result 一起使用,封装多个结果集
- @ResultMap:实现引用 @Result 定义的封装
- @One:实现一对一结果集封装
- @Many:实现一对多结果集封装
- @SelectProvider:实现动态 SQL 映射
- @CacheNamespace:实现注解二级缓存的使用
- @Param:通常传入 Mybatis 的只有一个,当传入 Mybatis 的参数大于或等于两个的时候,需要对传入的每一个参数进行注释,好让 Mybatis 将参数和 SQL 中的参数进行对应。
3. Mybatis 使用注意事项 - 以查询为例
根据上面的测试类和之前所以写的注解,会发现,我们根本没有写映射配置文件,那么,Mybatis 是怎么知道传入的数据类型和封装的数据类型呢?
我们原先的做法是:
在和 resources 文件夹下创建,和 java dao 类所在包,相同结构的文件夹组。并在该文件夹组下创建一个映射配置文件:
<?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.itheima.dao.IUserDao">
<!-- 查询所有 -->
<select id="findAll" resultType="user">
select * from user
</select>
</mapper>
而在这个配置文件中,父标签 mapper 中的 namespace 属性,和其字标签 select 标签中的 id 属性,都是该映射配置文件中的唯一标识。
而 select 标签天的 resultType 属性和 sql 语句,都是 Mybatis 在执行时,所需要用到的。
然后,打开现在使用注解的接口,此时,该项目中已经没有映射配置文件了:
package com.mybatis.dao;
import com.mybatis.domain.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 在 Mybatis 中针对 CRUD 一共有4个注解
* @Select @Insert @Delete @Update
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
}
其和之前的映射配置文件的差距在,映射配置文件啰里啰嗦写了一堆,而注解一句 sql 语句就搞定了。同时,这一句注解,将所有的映射配置文件中的事情都说清了。
仔细观察,会发现,select 注解注解的方法,其实是知道的。知道了该方法,同时也就能找到该方法的类(IUserDao)和该方法的全限定类名(com.mybatis.dao.IUserDao)。所以当这三个一出场,就是之前的映射配置文件中,所需要的那些属性。最后只剩一个结果类型,结果类型就在 List
中的 User。
这里需要注意,如果我我们的同时拥有映射配置文件和注解,运行 Mybatis 都会报错。
所以,我们在开发的时候,通常要么全部用注解,要么全部都用 xml 。
4. Mybatis 注解开发进行 CRUD 操作
4.1 使用 Mybatis 注解开发进行保存操作
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 保存用户 * @param user */ @Insert("insert into user (username, address, sex, birthday) values (#{username},#{address}, #{sex},#{birthday})") void saveUser(User user);
-
在测试类中进行测试:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testInsertUser() { User user = new User(); user.setAddress("江苏南通海安软件园NIIT培训中心"); user.setUsername("卢本伟牛逼"); user.setBirthday(new Date()); iUserDao.saveUser(user); } }
4.2 使用 Mybatis 注解开发进行更新操作
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 更新用户信息 * @param user */ @Update("update user set username = #{username}, sex = #{sex}, address = #{address}, birthday = #{birthday} where id = #{id}") void updateUser(User user);
-
然后,在测试类中进行测试:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testUpdateUser() { User user = new User(); user.setAddress("江苏南通海安软件园NIIT培训中心"); user.setUsername("卢本伟牛逼"); user.setBirthday(new Date()); user.setId(60); user.setSex("男"); iUserDao.updateUser(user); } }
4.3 使用 Mybatis 注解开发进行删除操作
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 根据 id 删除用户信息 * @param id */ @Update("delete from user where id = #{id}") void deleteUser(int id);
-
然后,在测试类中进行测试:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testDeleteUser() { int id = 60; iUserDao.deleteUser(id); } }
4.4 使用 Mybatis 注解开发进行查询操作
最后上面的引入已经使用了注解开发进行查询操作,这里不再赘述。
4.5 使用 Mybatis 注解开发进行查询单个用户信息操作
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 根据 id 查找用户 * @param id */ @Select("select * from user where id = #{id}") User findById(int id); }
-
然后,在测试类中进行测试:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testFindAll() { List<User> all = iUserDao.findAll(); for (User user: all) { System.out.println(user); } } @Test public void testFindById() { int id = 48; User byId = iUserDao.findById(id); System.out.println(byId); } }
4.6 使用 Mybatis 注解开发进行模糊查询操作
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 根据用户名进行模糊查询 * @param username * @return */ @Select("select * from user where username like #{username}") List<User> findUserByName(String username);
-
然后,在测试类中进行测试:
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testFindByName() { String username = "%王"; List<User> users = iUserDao.findUserByName(username); for (User user : users) { System.out.println(user); } } }
4.7 使用 Mybatis 注解开发进行总记录条数的查询
-
首先,在 dao 接口中创建一个新的方法,并且标志注解:
/** * 查询总用户数量 * @return */ @Select("select count(*) from user") int findTotal(); }
这里需要注意
count(*)
中间不能分开。count (*)
这样是错误的 -
在测试类中进行测试
package com.mybatis.test; import com.mybatis.dao.IUserDao; import com.mybatis.domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.List; public class AnnotationTest { private SqlSession sqlSession; private InputStream in; private IUserDao iUserDao; @Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(true); iUserDao = sqlSession.getMapper(IUserDao.class); } @After public void destroy() throws IOException { if (sqlSession != null) { sqlSession.close(); } if (in != null) { in.close(); } } @Test public void testFindTotal() { int total = iUserDao.findTotal(); System.out.println(total); } }
4.8 使用@Param对 Mybatis 中的多参数进行注解 - 修改于 2019/9/13
我们来看这样的一个例子,如果我们需要规定,查询时一次只查询出5条数据,在使用 SQL 语句时,我们的操作是:
select * from user limit 0, 5;
这一段 SQL 语句的意思是,查询区间为 (0, 5] 中的数据。
反应到注解上就是:
@Select("select * from user limit ${begin}, #{end}
List<User> findLimit(Integer begin, Integer end);
如果这么写,Mybatis 会报错,如果你传入的数据为 begin=0, end=10,那么错误信息为:
SQL 语句错误,错误在 "limit 10, null"
这里的原因就是,当你传入的参数只有一个的时候,没话讲,Mybatis 知道将你传入的数据填入到 #{}
中,但是,当你传入两个参数的时候,Mybatis 就知道怎么将参数和 #{}
进行对应。所以,需要 @Param
注解:
@Select("select * from user limit ${begin}, ${end})
List<User> findLimit(@Param("bengin") Integer begin, @Param("end") Integer end);
这个 @Param
注解稍微有一点奇怪,它注解在参数上面的。
5. Mybatis 进行多表操作(使用注解进行复杂关系映射的开发)
5.1 Mybatis 复杂关系映射注解的说明
5.1.1 Results 和 Result 注解
@Results 注解,代替的是标签resultMap
其中 id 为该封装结果集注解的唯一标识,value 为 @Result 数组。
该注解中可以使用单个 @Result 注解,也可以使用 @Result 集合
@Results ( values =
{
@Result(),
@Result()
}
)
@Result 注解
代替了 id 标签 和 result 标签
@Result 中的属性介绍:
-
id 是否是主键字段
-
column 数据库的列名
-
property 需要装配的属性名
-
one 需要使用的 @One 注解( @Result ( one = @One ( ) ) )
@Result( one = @One () )
-
many 需要使用的 @Many 注解 ( @Result ( many = @Many ( ) ) )
@Result( many = @Many() )
5.1.2 One 注解
@One 注解 (一对一, 多对一)
代替了 association 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
- select 指定用来多表查询的 sqlmapper
- fetchType 会覆盖全局的配置参数 lazyLoadingEnabled
使用格式:
@Result(
column = "",
property = "",
one = @One(select = "")
)
@Many 注解(一对多)
代替了 collection 标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理 “ 一对多 ” 的关系。需要指定映射的 Java 实体类的属性,属性的 javaType (一般为 ArrayList)但是注解中可以不定义
使用格式:
@Result ( property=" “, column=” ", many = @Many(select = " " ) )
@Result(
property="",
column="",
many=@Many(select="")
)
5.2 Mybatis 注解建立实体类属性和数据库表中列的对应关系
在使用注解时,可以通过使用 @Results 和 @Result 注解的组合,来完成当实体类属性名和数据库表中的列名不相同时,建立两者之间关系的任务。
在 Dao 接口的对应方法上,添加以下注释:
/**
* 查询所有用户
*
* @return
*/
@Select("select * from user")
@Results(value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(column = "birthday", property = "birthday"),
})
List<User> findAll();
(这里的实体类中的属性我并没有去修改)
但是,当我们我去使用通过 id 查找数据时,会发现数据并没有封装进去。这个时候,@Results 注解中的 id 属性派上用场了。
在 @Results 中写入 id 属性,并命名为 “userMap”:
/**
* 查询所有用户
*
* @return
*/
@Select("select * from user")
@Results(id = "userMap",value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(column = "birthday", property = "birthday"),
})
List<User> findAll();
然后,在 dao 接口中的通过 id 查询用户的方法就可以通过这个 id 获取配置信息(使用 @ResultMap 注解):
/**
* 根据 id 查找用户
* @param id
*/
@Select("select * from user where id = #{id}")
@ResultMap(value = {"userMap"})
User findById(int id);
5.3 Mybatis 注解开发一对一(多对一)查询操作
5.3.1 环境的搭建
在之前的环境的基础上,为了实现了一对一(多对一)的查询,我们得在数据库中额外创建一个表:
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);
5.3.2 Mybatis 注解开发一对一(多对一)查询操作
然后在 domain 文件下创建一个 Account 实体类:
package com.mybatis.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
// 多对一 ( mybatis 中称之为一对一 )的映射
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}
}
最后,在 dao 文件夹下创建 IAccount 接口文件:
package com.mybatis.dao;
import com.mybatis.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
public interface IAccountDao {
/**
* 查询所有账户,并且获取每个账户所属的用户信息
* @return
*/
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id=true, column = "id", property = "id"),
@Result(column = "uid", property = "uid"),
@Result(column = "money", property = "money"),
@Result(property = "user", column = "uid",
one = @One(
select = "com.mybatis.dao.IUserDao.findById",
fetchType = FetchType.EAGER
)
)
})
List<Account> findAll();
}
这里的 @One 注解是一对一的映射关系。
@Result(property = "user", column = "uid",
one = @One(
select = "com.mybatis.dao.IUserDao.findById",
fetchType = FetchType.EAGER
)
这一段代码的意思是:封装的是 user, 通过 uid 字段查询数据库中的user表, 使用的是一对一映射关系;在一对一映射关系中,select 表示通过 findByid 这个方法,使用 uid 字段,将查询出来的数据,封装到 User 对象中,fetchtype 表示是否是延迟查询。
5.4 Mybatis 注解开发一对多(多对多)查询操作
5.4.1 一对多
因为我们需要在查询用户的信息的同时,能够根据要求查询出账户的信息。所以首先,现在 User 实体类中,完成对 Account 实体类的映射。
package com.mybatis.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
// 一对多关系映射,一个用户对应多个账户
private List<Account> accounts;
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
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 getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
}
然后,在 IUserDao 这个接口中,在 @Results 注解中加入对 account 表的映射:
/**
* 查询所有用户
*
* @return
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(column = "birthday", property = "birthday"),
@Result(property = "accounts", column = "id", many = @Many(
select = "com.mybatis.dao.IAccountDao.findByUid",
fetchType = FetchType.LAZY
)
)
})
List<User> findAll();
这里的映射,指的是封装在 accounts 这个 List 容器中,通过使用 id 字段和 IAccount 接口中的 findByUid 方法,来查询出所需要的数据。使用的查询方式是延迟查询。
5.4.2 多对多
由于多对多中,在数据库中是有三张表的,其中两种表用来存储信息,而最后一张表用来存储另外两个表之间的关系。
这样的话,就必须通过 user_role,来查询两个的数据。实际上操作起来很像一对多。
- 首先在 User 实体类中创建对 Role 实体的引用:
private List<Role> roles; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; }
- 然后在 UserDao 中进行查询的 sql 语句的编辑:
这里的首先查询关系表中,和 Role 表有关系的 User 表中的数据。然后再通过 RoleDao 中的 findAllById 方法查询 Role 表中和 User 表有关的数据/** * 查找所有用户 * @return */ @Select("select * from user where id in (select uid from user_role )") @Results(id = "userMap" , value = { @Result(id = true, property = "id", column = "id"), @Result(property = "address", column = "address"), @Result(property = "sex", column = "sex"), @Result(property = "username", column = "username"), @Result(property = "birthday", column = "birthday"), @Result(property = "roles",column = "id", many = @Many( select = "review.mybatis.many.to.many.dao.RoleDao.findAllById", fetchType = FetchType.LAZY )) }) List<User> findAll();
- 接下来,在 AccountDao 接口类中,编写 findAllById 方法的 Sql 语句:
/** * 通过 uid 查询所有的角色信息 * @return */ @Select("select * from role where id in (select rid from user_role where uid = #{uid})") @Results(id = "roleMap", value = { @Result(id = true, property = "roleID", column = "id"), @Result(property = "roleName", column = "role_name"), @Result(property = "roleDesc", column = "role_desc") }) List<Role> findAllById(Integer uid);
- 最后,在测试类中运行的结果为:
User{id=41, address='北京', sex='男', username='老王', birthday=Tue Feb 27 17:47:08 CST 2018} Role{roleID=1, roleName='院长', roleDesc='管理整个学院'} Role{roleID=2, roleName='总裁', roleDesc='管理整个公司'} 2019-09-12 11:21:15,060 914 [ main] DEBUG o.many.dao.RoleDao.findAllById - ==> Preparing: select * from role where id in (select rid from user_role where uid = ?) 2019-09-12 11:21:15,060 914 [ main] DEBUG o.many.dao.RoleDao.findAllById - ==> Parameters: 45(Integer) 2019-09-12 11:21:15,061 915 [ main] DEBUG o.many.dao.RoleDao.findAllById - <== Total: 1 User{id=45, address='北京金燕龙', sex='男', username='传智播客', birthday=Sun Mar 04 12:04:06 CST 2018} Role{roleID=1, roleName='院长', roleDesc='管理整个学院'}
6. Mybatis 注解开发使用缓存
6.1 Mybatis 注解开发,使用一级缓存
一级缓存和使用配置文件一样,是不需要任何配置和注解的。直接使用即可。数据以 Map 的格式存放在 sqlSession 对象中。
如果使用的是对象存放数据,那么从 sqlSession 取出的数据为对象,每次取的为同一个对象。
6.2 Mybatis 注解开发,使用二级缓存
使用二级缓存,首先需要在主配置文件中的全局设置中,开启二级缓存(默认是开启的):
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
然后,在 IuserDao 接口类中,在类名处,写上注解:
@CacheNamespace(blocking = true)
public interface IUserDao {
/**
* 查询所有用户
*
* @return
*/
@Select("select * from user")
@Results(id = "userMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "sex", property = "sex"),
@Result(column = "address", property = "address"),
@Result(column = "birthday", property = "birthday"),
@Result(property = "accounts", column = "id", many = @Many(
select = "com.mybatis.dao.IAccountDao.findByUid",
fetchType = FetchType.LAZY
)
)
})
List<User> findAll();
/**
* 根据 id 查找用户
*
* @param id
*/
@Select("select * from user where id = #{id}")
@ResultMap(value = {"userMap"})
User findById(int id);
/**
* 根据用户名进行模糊查询
*
* @param username
* @return
*/
@Select("select * from user where username like #{username}")
@ResultMap(value = {"userMap"})
List<User> findUserByName(String username);
}
最后,在测试类中进行测试:
package com.mybatis.test;
import com.mybatis.dao.IUserDao;
import com.mybatis.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class SecondLevelCacheTest {
private SqlSession sqlSession;
private InputStream in;
private IUserDao iUserDao;
private SqlSessionFactory factory;
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
sqlSession = factory.openSession(true);
iUserDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
// if (sqlSession != null) {
// sqlSession.close();
// }
if (in != null) {
in.close();
}
}
@Test
public void testFindOne() {
User user = iUserDao.findById(48);
System.out.println(user);
sqlSession.close();
SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
User user1 = userDao.findById(48);
System.out.println(user1);
session.close();
sqlSession.close();
}
}
由于这里使用的是同一个 SqlSessionFactory 所以,二级缓存存放的位置一样。
这里是最后的结果:
2019-09-10 20:01:48,870 103 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Opening JDBC Connection
2019-09-10 20:01:49,731 964 [ main] DEBUG source.pooled.PooledDataSource - Created connection 769798433.
2019-09-10 20:01:49,735 968 [ main] DEBUG .mybatis.dao.IUserDao.findById - ==> Preparing: select * from user where id = ?
2019-09-10 20:01:49,767 1000 [ main] DEBUG .mybatis.dao.IUserDao.findById - ==> Parameters: 48(Integer)
2019-09-10 20:01:49,813 1046 [ main] DEBUG .mybatis.dao.IUserDao.findById - <== Total: 1
2019-09-10 20:01:49,814 1047 [ main] DEBUG atis.dao.IAccountDao.findByUid - ==> Preparing: select * from account where uid = ?
2019-09-10 20:01:49,814 1047 [ main] DEBUG atis.dao.IAccountDao.findByUid - ==> Parameters: 48(Integer)
2019-09-10 20:01:49,817 1050 [ main] DEBUG atis.dao.IAccountDao.findByUid - <== Total: 0
User{id=48, username='小马宝莉', address='北京修正', sex='女', birthday=Thu Mar 08 11:44:00 CST 2018}
2019-09-10 20:01:49,824 1057 [ main] DEBUG ansaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2de23121]
2019-09-10 20:01:49,824 1057 [ main] DEBUG source.pooled.PooledDataSource - Returned connection 769798433 to pool.
2019-09-10 20:01:49,828 1061 [ main] DEBUG com.mybatis.dao.IUserDao - Cache Hit Ratio [com.mybatis.dao.IUserDao]: 0.5
User{id=48, username='小马宝莉', address='北京修正', sex='女', birthday=Thu Mar 08 11:44:00 CST 2018}
发现,在查询过一次之后,并没有查询第二次,而是在缓存中查询。(缓存命中率为0.5)