MyBatis 入门
MyBatis
MyBatis 是一个简化和实现了 Java 数据持久化层(persistence layer)的开源框架,它抽象了大量的 JDBC 冗余 代码,并提供了一个简单、易用的 API 和数据库交互
JDBC 编程问题
在 Java EE 应用开发中,必不可免的要与数据库打交道,与数据库的交互一般都是通过定一个 DAO 层(数据库 访问)来完成这个操作。定义 DAO 层的主要是原因是为了解决在开发过程中业务与数据的分离,可以在不同的层次 上解决不同的问题,而不至于让所有代码冗余在一起,不利于后期的维护。
但是,即使在开发中进行了分层,使用单纯的 Java 的 JDBC 编程仍然存在大量的问题。如下代码所示:
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
String sql = "select * from user where user_name=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "zhangsan");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
User us = new User();
us.setUserId(rs.getInt("user_id"));
us.setUsername(rs.getString("user_name"));
us.setPassword(rs.getString("user_pass"));
us.setCname(rs.getString("cname"));
}
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
如上述代码中所示,在进行 JDBC 编程时就会存在如下问题.
-
数据库的连接,使用时就需要连接,不需要就关闭,频繁的打开与关闭连接,势必会造成数据库资源的浪费, 降低数据库的性能。
解决方案:数据库连接池。
-
将要执行的 SQL 语句以硬编码的方式写入到程序中,如果需求发生变动,则需要更改代码,不利于系统的维 护。
解决方案:将 SQL 语句配置在 XML 文件中,即使 SQL 语句更改,也不需要更改 Java 源程序。
-
如果执行的 SQL 中含有占位符,则需要向执行 SQL 语句的 PreparedStatement 对象中传入参数,如果查询 条件发生更改,则占位符的数目也需要改动,那么传入的参数个数会随之发生变动。
解决方案:将执行 SQL 语句中的占位符、参数,同样配置在 XML 文件中。
-
执行查询后得到的 ResultSet 结果集中,在获取数据时将数据表的字段名称,同样以硬编码的方式写死在程 序中,不利于系统的维护。
解决方案:将查询的结果集,自动映射为 Java 对象。
以上所有问题的解决,就是数据库持久层框架存在的意义,而 mybatis 这个访问数据库持久层框架就很好的解 决了以上所有的问题。
- 结构示意图
应用
根据 ID 查询用户
MyBatis 框架几乎消除了 JDBC 的所有代码,并提供 XML 配置或注解的方式来映射接口和 POJO,下面以 XML 的方式来配置 JDBC 层的操作。MyBatis 框架提供 mapper.xml 文件用于配置要执行的 SQL 操作,并将执行 SQL 时 要传入的参数,以及传出的参数完成了映射。下面列出包含根据 ID 查询用户的 mapper 文件,内容如下:
- mapper.xml 配置文件,文件名为 user-mapper.xml
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="user">
<select id="findUserById" parameterType="int" resultType="com.langsin.user.pojo.User">
select * from user where user_id=#{userId}
</select>
</mapper>
mapper.xml 文件的根标签为<mapper>,<mapper>标签提供给了 namespace 属性用于指定命名空间,用于 区分不同 mapper 文件中的 SQL,非常类似于 Java 中 pageckage 包的概念。namespace 属性值可以自己定义,但 如果后续使用 mapper 高级代理的方式,namespace 属性值则有特殊的作用。
标签用于查询操作,id 属性为 select 的唯一标识,可以看成是在 dao 层定义的方法名,属性 parameterType 是输入映射,表示执行 SQL 时要接收的参数的数据类型,参数类型可以是 Java 的各种数据类型, resultType 是执行 SQL 语句后,将查询结果集中的每条记录映射成的输出类型,可以是 Java 中的各种类型,如果是 一个 Java Bean 类,则需要指定 Java Bean 的全限定名。
标签中包含了要执行的 SQL 语句,SQL 语句中如果需要传参,则使用占位符的方式,在 MyBatis 框架 中使用#{}的方式表示占位符,其中大花括号中填写的是对应的参数值,如果是基本数据类型,可以填写任意名称,但 是应该遵循开发规范写参数名,比如:userId。
MyBatis 框架解析 mapper 文件时,会将配置在内的 SQL 语句封装成一个可以执行的 MappedStatement 对象, 并将这些对象在MyBatis框架中进行管理。程序执行时,需要将mapper文件整合在全局配置文件sqlMapConfig.xml 文件中,全配置文件中提供了标签将程序中的 mapper 文件整合在一起。配置如下:
- sqlMapConfig.xml 配置文件
<!-- 在全局配置文件中引入 mapper 文件 -->
<mappers>
<mapper resource="mapper/user-mapper.xml"/>
</mappers>
- 测试程序如下:
@Test
public void testFindUserById() throws Exception {
// 读取全局配置文件
InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 创建一个 SqlSession 会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
// 创建会话对象
SqlSession session = factory.openSession();
// 执行查询操作
User user = session.selectOne("user.findUserById", 11);
System.out.println(user.getUserId());
}
程序解析:
在上述程序中,通过 SqlSession 对象的 selectOne 方法进行查询单条记录,此方法需要两个参数, 第一个参数为 statementId,在配置 mapper.xml 文件时,<select>标签的 id 属性为 SQL 语句的标识,但是 mapper 文件中配置的 SQL 在执行时,最终会被 MyBatis 封装到一个 MappedStatement 对象中去,而这个 id 也就是 MappedStatement 对象的 id,这个参数的字符格式为:命名空间+“.”+属性 ID 的方式,例如:user.findUserById。 注意:在创建对应的 JavaBean 时,MyBatis 默认是以数据库表中字段列名与 Java Bean 中的成员变量名进行对 应的,所以在定义成员变量名是需要与表字段的列名保持一致。
根据用户名查询
在用户查询操作中,如果根据用户名进行查询,大多数情况下都是进行模糊查询操作,在 MyBatis 中如果进行 模糊查询操作,只需要将 SQL 语句更改为模糊查询即可。如下所示:
- mapper.xml 配置文件
<select id="findUserByName" parameterType="String" resultType="com.langsin.user.pojo.User">
select * from user where cname like '%${value}%'
</select>
注意:在上述的 SQL 语句中没有使用#{}占位符的方式,因为这里进行模糊匹配查询要用到%号,要进行字符串 拼接,所以使用了 '%${value}%'。因为传入的参数为 String 类型,是引用类型,不是基本数据类型所以大括号中只 能使用 value,而不能使用其他变量名。使用${value}的方式,表示对参数不做任何修饰直接进行 SQL 拼接,但这样 做具有一定的危险性,会导致程序安全性能降低,原因就是 SQL 注入,就是因为使用了字符串拼接。因此不建议使 用。
测试代码:
@Test
public void testFindUserByName() throws Exception {
InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
SqlSession session = factory.openSession();
List<User> userList = session.selectList("user.findUserByName", "李");
for (User user : userList) {
System.out.println(user.getCname());
}
}
程序解析:模糊查询的结果可能不止一条记录,所以不能再使用 SqlSession 对象的 selectOne 方法,该方法只 能将查询结果为一条记录的结果集进行转换,而如果是多条则会抛出异常。可以使用 selectList 方法,此方法可以将 查询结果集映射为一个集合,返回一个 List 集合。
将上面程序进行改造,更改为使用#{}占位符的方式,只不过需要程序开发人员在对传入的参数进行处理,更改 为“%参数%”的方式。
- mapper.xml 配置文件
<select id="findUserByName" parameterType="String" resultType="com.langsin.user.pojo.User">
select * from user where cname like #{value}
</select>
测试代码:
@Test
public void testFindUserByName() throws Exception {
InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
SqlSession session = factory.openSession();
List<User> userList = session.selectList("user.findUserByName", "%李%");
for (User user : userList) {
System.out.println(user.getCname());
}
}
用户添加操作
使用 MyBatis 进行插入操作与查询操作类似,都是相同的流程,只不过在 mapper.xml 配置文件中使用的标签 不同,调用的 SqlSession 的方法不同而已。添加数据使<insert>标签,对应的是 SqlSession 对象的 insert()方法。 使用标签添加一条记录时,需要传入多个参数,此时可以使用 Java Bean 对象,把多个参数封装在 Java Bean 中,再把 Java Bean 对象作为参数整体传入。在 SQL 语句中,占位符的大括号中,填写的就是 Java Bean 对象的成 员变量名。
- mapper.xml 配置文件
<!-- 配置 insert 语句 -->
<insert id="addUser" parameterType="com.langsin.user.pojo.User" >
insert into user values(null, #{cname}, #{password}, #{username}, #{money})
</insert>
测试程序:
@Test
public void testAddUser() throws Exception{
User user = new User("白居易", "baijuyi", "123456");
int row = session.insert("user.addUser", user);
System.out.println("影响行数:"+row);
session.commit();
session.close();
}
程序解析:进行数据添加操作时,不要忘记将事务提交,因为 MyBatis 框架模式是开启事务的,所以要调用 SqlSession 对象的 commit()方法进行提交
返回自增长主键
在软件开发时,如果往某张表中插入一条数据记录,而该表的主键又是自增长的设置,那么在执行 insert 语句 时,我们一般不会对主键列做操作,主键列会自动生成并插入到表中。但是主键列的值对于应用来说又是必要的字段, 因为可能需要根据主键再去执行相关的修改或者删除操作。如果使用 MyBatis 框架开发,在插入一条数据时,该如何 获得这条记录的自动生成的主键呢?
如果表的主键是自增长设置,那么 mysql 在执行 insert 插入提交之前,会自动生成一个自增长主键,并插入到 主键列上。获取自增长的主键列的值,可以通过 mysql 的提供的函数 last_insert_id()可以获取到该值,但是此函数 的执行必须要在 insert 语句执行之后才可以,所以在 MyBatis 中如果想要获取刚插入的子增长主键记录,则在 mapper.xml 配置文件中,使用如下配置:
- mapper.xml 配置文件
<!-- 配置 insert 语句 -->
<insert id="addUser" parameterType="com.langsin.user.pojo.User" >
<selectKey keyProperty="user_id" order="AFTER" resultType="int">
select last_insert_id()
</selectKey>
insert into user values(#{user_id}, #{cname}, #{password}, #{username}, #{money})
</insert>
标签用于查询主键,keyProperty 属性的作用:是将查询之后得到的主键值设置到 parameterType 输入映射传入的 Java 对象的指定属性中。order 的作用是指定 selectKey 元素的查询是在 SQL 语句之前执行还是在 之后执行,可选值为:BEFORE、AFTER,分别表示 SQL 语句前执行与 SQL 语句后执行。
注意:这种方式只适用于自增长主键表的插入操作.
测试程序:
@Test
public void testAddUser() throws Exception{
User user = new User("王维", "wangwei", "123456");
int row = session.insert("user.addUser", user);
session.commit();
session.close();
System.out.println("影响行数:"+row+",返回自增长主键:"+user.getUser_id());
}
返回非自增长主键
往某张表中插入一条数据,主键是非自增长的 varchar 类型,生成主键时可以调用 mysql 数据库的 uuid()函数, 该函数可以获取一个唯一的随机的字符串,长度为 36 位,可以利用此函数的值当做表的主键。如果要想使用 MyBatis 来获取此值,在 mapper 文件中使用如下配置:
- mapper.xml 配置文件
<!-- 配置 insert 语句 -->
<insert id="addUser" parameterType="com.langsin.user.pojo.User" >
<selectKey keyProperty="user_id" order="BEFORE" resultType="String">
select uuid()
</selectKey>
insert into user values(#{user_id}, #{cname}, #{password}, #{username}, #{money})
</insert>
注意:在上面的配置中执行 uuid 函数的操作需要在执行 insert 语句之前,需要先生成主键值再进行插入操作, 因此 order 的值为 before。resultType 为执行查询之后的返回值类型 String。
测试程序同上。
删除用户操作
在 mapper 文件中,如果要进行删除操作,就需要使用标签。在标签中,同样配置 id 属性, 输入映射 parameterType 属性。调用的方法为 SqlSession 对象的 delete 方法,配置信息如下。
- mapper.xml 配置文件
<!-- 配置 delete 删除语句 -->
<delete id="deleteUser" parameterType="int">
delete from user where user_id=#{userId}
</delete>
- 测试程序:
@Test
public void testDeleteUser() throws Exception{
int row = session.delete("user.deleteUser", 12);
session.commit();
System.out.println("影响行数:"+row);
}
- 更新用户操作
在 mapper 文件中,如果要进行更新操作,就需要使用标签。在标签中,同样配置 id 属 性,输入映射 parameterType 属性。调用的方法为 SqlSession 对象的 update 方法,配置信息如下。
- mapper.xml 配置文件
<!-- 配置 update 更新语句 -->
<update id="updateUser" parameterType="com.langsin.user.pojo.User">
update user set username=#{username},money=#{money} where user_id=#{user_id}
</update>
- 示例代码
@Test
public void testUpdateUser() throws Exception{
User user = new User("白居易", "xiaobai", "abc123");
user.setMoney(new BigDecimal("2000"));
user.setUser_id(15);
int row = session.update("user.updateUser", user);
session.commit();
System.out.println("影响行数:"+row);
}
{}与${}的区别
#{}符号是 MyBatis 的占位符,相当于 jdbc 中的“?”,在传参时在参数值外面添加一对单引号。
例如:
select * from user where cname=#{value}
转换后
select * from user where cname='张三'
${}符号是 MyBatis 的拼接符,对传入的参数不做任何修饰,直接进行简单的字符串拼接。
例如:
select * from user where cname=${value}
转换后
select * from user where cname=张三
selectOne 与 selectList
MyBatis 开发 Dao
定义 Dao 接口
首先定义一个该功能模块的 Dao 接口,这个无须多说,无非是该功能下的一系列方法的声明而已。接下来主要 是该 Dao 接口的实现。
- UserDao 接口:
public interface UserDao {
/** 添加用户操作 */
public int addUser(User user);
/** 删除用户操作 */
public int deleteUser(Integer userId);
/** 根据 ID 查询用户 */
public User queryUserById(Integer userId);
/** 查询所有用户数据 */
public List<User> queryUserList();
}
实现 Dao 接口
使用 MyBatis 进行数据库的开发主要涉及到以下几个类: SqlSessionFactory:SqlSession 会话的创建工厂,MyBatis 主要是利用该类完成 SqlSession 对象的创建。因 此该类只需要一个就可以,在 Spring 的 IoC 容器中考虑使用单例模式管理。
SqlSessionFactoryBuilder:SqlSessionFactory 的创建需要依赖此工具类进行创建,因此只需要当成一个工具 类使用,不需要 Spring 进行管理。
SqlSession:MyBatis 主要使用该类完成一系列的数据库操作,包括增加、删除、修改、查询。但是 SqlSession 是一个线程不安全的,因此使用时为了防止出现并发访问导致的数据状态信息不一致的情况,需要将 SqlSession 定 义成方法局部变量来使用。
- UserDaoImpl 实现类
public class UserDaoImpl implements UserDao {
private SqlSessionFactory factory;
public void setFactory(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public int addUser(User user) {
SqlSession session = this.factory.openSession();
int row = session.insert("user.addUser", user);
session.commit();
session.close();
return row;
}
@Override
public int deleteUser(Integer userId) {
SqlSession session = this.factory.openSession();
int row = session.insert("user.deleteUser", userId);
session.commit();
session.close();
return row;
}
@Override
public User queryUserById(Integer userId) {
SqlSession session = this.factory.openSession();
User user = session.selectOne("user.findUserById", userId);
session.close();
return user;
}
@Override
public List<User> queryUserList() {
SqlSession session = this.factory.openSession();
List<User> userList = session.selectList("user.queryUserAll");
session.close();
return userList;
}
}
测试 Dao 实现类
Dao 接口类定义完成后,使用 JUnit 单元进行测试,测试代码如下:
- 测试代码:
@Test
public void testUpdateUser() throws Exception{
User user = new User("白居易", "xiaobai", "abc123");
user.setMoney(new BigDecimal("2000"));
InputStream stream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 创建一个 SqlSession 会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(stream);
UserDao dao = new UserDaoImpl(factory);
dao.addUser(user);
}
上面使用 MyBatis 开发的 Dao 被称为原始 Dao 的开发,但这种使用存在以下几 个问题:
- dao 接口的实现类中存在大量的重复代码,是否可以将代码提取出来,减轻开发的工作量。
- 调用 SqlSession 方法时,将 mapper 配置中的 statement 的 id 硬编码到 Dao 程序中。
- 调用 SqlSession 方法时传入的变量,由于 SqlSession 使用了泛型 ,即使变量传入错误,也不会引发编译 异常,不利于程序人员的开发。
由于以上问题的存在,所以使用 MyBatis 开发一个优秀的 Dao 层结构,可以使用 mapper 高级代理的方式
高级 Mapper 代理
使用 MyBatis 高级Mapper 代理的方式,只需要定义接口即可,不用再为接口提供实现类。为了体现高级 Mapper 代理的方式,可以将接口名使用 Mapper 进行结尾。但是仍然要编写 mapper.xml 配置文件。而且 mapper.xml 配 置文件需要遵循一定的开发规范才可以:
1、定义 Mapper 接口,并将 mapper.xml 文件的 namespace 命名空间指定 Mapper
- Mapper 接口:
public interface UserMapper {
/** 添加用户操作 */
public int addUser(User user);
/** 删除用户操作 */
public int deleteUser(Integer userId);
/** 更新用户操作 */
public int updateUser(User user);
/** 根据 ID 查询用户 */
public User queryUserById(Integer userId);
/** 查询所有用户数据 */
public List<User> queryUserList();
}
- mapper.xml 配置文件
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间 namespace 如果使用 mapper 代理,需要指定 mapper 的地址
-->
<mapper namespace="com.langsin.user.dao.UserMapper">
<select id="findUserById" parameterType="int" resultType="com.langsin.user.pojo.User">
select * from user where user_id=#{userId}
</select>
<select id="queryUserList" resultType="com.langsin.user.pojo.User">
select * from user
</select>
<!-- 配置 insert 插入语句 -->
<insert id="addUser" parameterType="com.langsin.user.pojo.User" >
<selectKey keyProperty="user_id" order="AFTER" resultType="int">
select last_insert_id()
</selectKey>
insert into user values(#{user_id}, #{cname}, #{password}, #{username}, #{money})
</insert>
<!-- 配置 delete 删除语句 -->
<delete id="deleteUser" parameterType="int">
delete from user where user_id=#{userId}
</delete>
<!-- 配置 update 更新语句 -->
<update id="updateUser" parameterType="com.langsin.user.pojo.User">
update user set username=#{username},money=#{money} where user_id=#{user_id}
</update>
</mapper>
2、Mapper 接口中的方法名要与配置文件 mapper.xml 中的 Statement 对象的 id 一致
- Mapper 接口方法名:
public interface UserMapper {
/** 添加用户操作 */
public int addUser(User user);
}
- mapper.xml 配置文件
<!-- 配置 insert 插入语句 -->
<insert id="addUser" parameterType="com.langsin.user.pojo.User" >
<selectKey keyProperty="user_id" order="AFTER" resultType="int">
select last_insert_id()
</selectKey>
insert into user values(#{user_id}, #{cname}, #{password}, #{username}, #{money})
</insert
3、mapper 接口中方法的输入参数要与 mapper.xml 配置文件中的 parameterType 保持一致。
4、mapper 接口中方法的返回值类型要与 mapper.xml 配置文件中的 resultType 保持一致。 只有遵循了如上所述的开发规范,MyBatis框架才可以自动的实现Mapper接口的高级代理。通过SqlSession 的 getMapper 方法,即可获得 Mapper 接口的动态代理对象,此方法执行需要接口的 Class 对象作为参数。如果查询 返回的是结果集合,只需要将 Mapper 接口中声明的返回值类型是 List 集合即可。这样 MyBatis 的实现的动态代理 对象内部就能判断在进行查询时是调用 selectOne 还是 selectList 方法了。
- 测试代码:
@Test
public void testUserMapper() throws Exception{
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.queryUserList();
for (User user : userList) {
System.out.println(user.getCname());
}
}
通过高级 Mapper 代理的方式,可以看到使用 MyBatis 框架已经可以消除掉 Dao 层的绝大部分代码,再进行 Dao 层编写时只需要定义接口,以及编写 mapper.xml 文件即可。不仅提升了程序开发人员的开发效率,还可以让 SQL 变的更加灵活。
mapper 映射
在 SqlMapConfig.xml 全局配置文件中,通过 mappers 标签下的子元素 mapper 或者 package 来加载其他的 mapper.xml 文件配置。使用 mapper 标签,可以通过两种方式来加载 mapper.xml 文件。
1、通过 resource 属性加 载 mapper 配置文件;
<!-- 在全局配置文件中引入 mapper 文件 -->
<mappers>
<mapper resource="mapper/user-mapper.xml"/>
</mappers>
注意:使用 resource 属性加载单个配置文件,文件路径是在类加载路径下开始的。
2、通过 class 属性加载 mapper 配置;
在 mapper 元素中,可以通过 class 属性指定 mapper 接口的方式,来加载映射 mapper.xml,但是要遵循一定 的规范。
- 首先必须遵循 mapper 高级代理的规范。
- mapper 的接口名必须与 mapper 文件名保持一致。
- 必须在同一文件夹下
<mappers>
<mapper class="com.langsin.user.dao.UserMapper"/>
</mappers>
注意:使用 MyBatis 框架加载 mapper 配置文件时,如果发现是通过 mapper 元素的 class 属性的方式去加载, 则会在该接口的所在包下扫描 mapper 文件并去加载该配置文件。如果 mapper 配置文件与 mapper 接口名不一致, 则不加载。
3、批量加载方式 通过使用package元素的name属性指定mapper接口所在的包名,MyBatis自动扫描该包下的所有的mapper 接口,并查找与接口名相同的 mapper 配置文件进行加载。前提是也需要遵循上面 2 中的规范,这是推荐使用方式.
<mappers>
<mapper name="com.langsin.user.dao"/>
</mappers>
输入映射
使用 MyBatis 所做的增加、删除、修改、查询的操作中,传入参数的输入映射都是简单的 Java 类型,比如 int、 String 等,稍微复杂一点的也就是一个简单的自定义的 Java Bean 对象即(POJO—Plain Ordinary Java Object), 如果为了支持一个复杂的多表联合查询、嵌套子查询等,需要传入的参数可能不是针对一个表的查询条件,在这种情 况下,就需要指定自定义包装类型的 POJO
使用 HashMap 集合
如果在执行 SQL 语句时,传入的参数较为复杂,是针对好几个表的查询条件,而在开发时针对每张表都会定义 一个与之对应的 Java Bean 对象。但现在却是多表的操作,而且条件也是针对不同表中的字段进行过滤查询,这时可 以考虑使用 HashMap 集合作为 parameterType 类型传入,其中#{}中的字符为 map 的 key 值即可
- Mapper 接口
public interface UserMapper {
/** 查询所有用户数据 */
public List<User> queryUserByName(HashMap<String,String> map);
}
- mapper.xml 文件
<select id="queryUserByName" parameterType="hashMap" resultType="user">
select * from user where cname like #{cname}
</select>
测试程序
@Test
public void testUserMapper() throws Exception{
UserMapper mapper = session.getMapper(UserMapper.class);
HashMap<String,String> map = new HashMap<>();
map.put("cname", "%李%");
List<User> userList = mapper.queryUserByName(map);
for (User user : userList) {
System.out.println(user.getCname());
}
}
在 Java 基础集合框架的学习中,我们知道 HashMap 的 key 值和 value 值都可以是任意的 Java 类型,绝大部 分情况下,key 值都是字符串,而value值则是花样繁多。在MyBatis 中,如果 parameterType的传入映射是 hashMap 而 value 值则是一个 Java 对象,不是 String 简单字符串时,可以使用 OGNL 的方式在占位符中寻值。???存疑
- Mapper 接口
public interface UserMapper {
/** 查询所有用户数据 */
public List<User> queryUserList(HashMap<String,User> map);
}
- mapper.xml 文件
<select id="queryUserList" parameterType="hashMap" resultType="user">
select * from user where cname like #{user.cname}
</select>
程序解析:使用高级 Mapper 代理的方式,传入参数为 hashMap 集合对象,用来作为 paramterType 的输入 映射,在集合中放入的 key 值是 user,对应的 value 值是 User 对象。在 mapper.xml 配置文件中,根据 OGNL 对 象导航图语句方式,应该是 user.cname 找到 User 对象中的 cname 属性传入。
- 测试程序:
@Test
public void testUserMapper() throws Exception{
UserMapper mapper = session.getMapper(UserMapper.class);
HashMap<String,User> map = new HashMap<>();
User user = new User();
user.setCname("%李%");
map.put("user", user);
List<User> userList = mapper.queryUserList(map);
for (User userObj : userList) {
System.out.println(userObj.getCname());
}
}
使用 POJO 自定义类型
MyBatis 框架除了可以使用 hashMap 集合作为传入参数,用来作为 parameterType 的输入映射外,还为程序 开发人员提供了另外一种方式,即自定义 POJO 类型,但最底层原理都是相通的。
例如:查询购买某种商品的所有的“王”姓用户的信息,则查询条件既有 user 表中信息,又有 item 商品表中 信息。
在此种情况下,不要修改任意的一个 POJO 对象,因为 POJO 对象是与表一一对应,而是在原有基础上扩展即 可。 可以定义一个视图对象 Vo,让它来继承其中一个 POJO,下面给出了 UserVO
- UserVo 扩展类
public class UserVo extends User{
private String itemName;
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
}
- mapper.xml 配置文件
<!-- 根据商品名称查询 -->
<select id="queryUserByItemName" parameterType="userVo" resultType="user">
select * from user join item on user.user_id=item.user_id where item.name=#{itemName} and
user.cname like #{cname}
</select>
- Mapper接口代码
public interface UserMapper {
public List<User> queryUserByItemName(UserVo vo);
}
- 测试代码:
@Test
public void testUserMapper() throws Exception{
UserMapper mapper = session.getMapper(UserMapper.class);
UserVo vo = new UserVo();
vo.setCname("%李%");
vo.setItemName("牛奶");
List<User> userList = mapper.queryUserByItemName(vo);
for (User userObj : userList) {
System.out.println(userObj.getCname());
}
}
程序解析:以组合方式扩展的 UserVo 对象,作为传入参数,用来当作 parameterType 属性的输入映射,在使 用 UserVo 对象中的 Item 属性时,仍然使用 OGNL 对象导航图语句的方式,首先找到 UserVo 对象的 item 属性,item 属性对应的是一个 Item 对象,再使用 Item 对象中的
输出映射
MyBatis 的输出映射支持两种形式,一种形式为 resultType,将查询结果对应到一个 POJO 上;另一种形式为 resultMap,需要使用 resultMap 标签先定义映射关系,然后再通过 resultMap 引用这个映射。
使用 resultType 映射
前面示例的所有操作,返回值类型映射都是使用 resultType 这种形式完成的,不管是返回单个 Java 对象还是返 回 Java 的 List 集合,resultType 都是指向一个 Java Bean 对象。
只不过具体是返回一个对象还是返回一个集合必须在 Mapper 接口的方法返回值类型中明确指明。这是因为 MyBatis 在生成的动态代理对象中,是根据返回值类型来确定是调用 selectOne 方法还是 selectList 方法。
需要注意的是,Java Bean 的属性名称必须与查询结果的字段表保持一致才可以映射成功,如果对应不一致的, 则 Java Bean 对应的属性的值采用默认值,基本类型为 0 或 false,引用类型为 null。
resultType 属性还可以指定为简单类型,如 Java 的基本数据类型或 String 字符串。例如:获取查询结果的所有 记录的总条数,就可以指定为 int 类型。
<select id="queryTotalCount" resultType="int">
select count(id) from user
</select>
resultType 属性还可以指定为 hashMap,无非就是将查询的结果的每条记录,都映射为一个 HashMap 对象, 字段名为 key 值,字段值为 value 值。
注意:如果是执行联合查询的结果,查询数据是从多个表中筛选出来的,而且字段名也进行了重命名,则可以考 虑使用 resultType 的属性为 hashMap。因为使用 hashMap 不需要考虑字段名,映射为 hashMap 对象时,是以最 终的查询结果的字段名为准作为 key 值对应的。
使用 resultMap 映射
在 MyBatis 框架中,可以使用 resultMap 属性完成高级输出结果的映射。例如:查询结果的列名与 POJO 对象 的属性名不一致,可以通过定义一个 resultMap 对查询结果列名和 POJO 属性名之间做一个映射关系。
首先定义一个 resultMap 元素,该元素包含两个主要属性,type 和 id,type 表示该 resultMap 元素所对应的 POJO 的类型,id 表示 resultMap 的唯一标识名。在 resultMap 元素下还包括两个重要子元素。
- id 元素:查询结果集的唯一标识列,即主键列。
- result 元素:查询结果集的普通列。
两个元素都包含相同的属性:其中主要使用 column 和 property 属性,column 表示查询结果的列名,property 表示 resultMap 元素 type 属性所对应的 POJO 的属性名。
映射完成后,则可以在其他 select 元素中的 resultMap 属性中引用该 resultMap定义的高级映射。
- mapper.xml 配置文件
<resultMap type="user" id="userMap">
<id column="user_id" property="userId"/>
<result column="cname" property="cname"/>
</resultMap>
<select id="queryUserById" parameterType="int" resultMap="userMap">
select * from user where user_id=#{userId}
</select>
程序解析:id 元素用于定义查询结果的唯一标识,如果查询结果的主键是由多列组成,那么需要写多个 id 元素, result 对应普通的列,有多少个列就需要定义多少个 result 元素。如果查询结果的列名跟 POJO 的成员变量名一致, 那么这个 result 可以省略。 在上面 mapper.xml 配置中,select 元素的 resultMap 属性指定了 resultMap 元素的 id 属性值,通过关联,select 语句执行查询后,返回的数据映射类型为 user 所对应的 POJO类型。