前言
在之前的一篇博文中,博主搭建了MyBatis开发环境,学习了MyBatis的核心类和接口的作用域及其生命周期,并叙述了核心配置文件的结构,利用SQL映射文件实现了简单的查询。今天具体分享一下关于SQL映射文件的学习,利用SQL映射文件实现较复杂的查询,以及增删改。
在数据库新建表UserInfo,字段如下,新建实体类User,属性与数据库表的字段对应,省略实体类User的代码。
一、SQL映射文件实现条件查询
以下示例中返回的实体类数据类型,即resultType属性或resultMap中的type属性,都应该写成对应类的包路径,只不过博主在核心配置文件配置指向了存放实体类的包,所以直接写了实体类的名字,这样比较简便。具体配置可自行百度,或参考上一篇博文,很简单,一句话的事。
(1)传入普通字段参数
新建UserMapper.java接口,定义查询用户的方法,使用注解传入参数
package Dao;
import Entity.User;
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
public User getUserInfo(@Param("uName")String name); //通过用户名查询用户
}
新建UserMapper.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指定了命名空间-->
<mapper namespace="Dao.UserMapper">
<!--验证用户登录-->
<select id="getUserInfo" resultType="User">
SELECT * FROM UserInfo WHERE UserName=#{uName}
</select>
</mapper>
映射文件中mapper标签的namespace属性值要对应着UserMapper接口的包路径,因为我们是通过SqlSession的mapper接口的方式执行SQL语句,而且select标签的id属性值要与接口中定义的方法名一致,否则会找不到映射的SQL标签。使用注解的方式传入参数,在SQL语句中要使用这个参数的时候,通过#{参数名}获得值(注意是@Param定义的参数别名,而不是传给接口的参数名称),这里是#{uName}。指定resultType返回类型是我们的实体类User,注意此时数据库的字段名一定要跟实体类的属性名保持一致,实体类属性名最好是驼峰命名法进行命名,至于属性名跟字段名不一致的情况,接下来会介绍。传入字段参数,可以省略parameterType属性,直接在SQL语句中通过#{参数名}使用即可,此属性表明执行这个SQL映射块的时候需要传入的参数是什么类型的,如要声明,则类型一定要匹配上。参数可以是多个。
在上一篇中我们定义了专门获取SqlSession、关闭SqlSession的工具类,这里不再列出。定义服务层代码UserServiceImpl.java(其实还有服务接口,自行创建,这里不再列出,节约篇幅),如下
package ServiceImpl;
import BaseUtil.MyBatisUtil;
import Dao.UserMapper;
import Entity.User;
import Service.UserService;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.SqlSession;
/**
* 用户服务接口实现类
* */
public class UserServiceImpl implements UserService {
/**
* 查询用户信息
* */
@Override
public User getUserInfo(@Param("uName") String userName) {
SqlSession sqlSession=MyBatisUtil.getSqlSession();
User user=sqlSession.getMapper(UserMapper.class).getUserInfo(userName);
MyBatisUtil.close(sqlSession);
return user;
}
}
最后创建测试代码Test.java
package Servlet;
import Entity.User;
import ServiceImpl.UserServiceImpl;
public class Test {
public static void main(String[] args) {
User user=new UserServiceImpl().getUserInfo("张三");
System.out.println(user.getUserName());
}
}
运行结果是可以输出字符串“张三”的,因为数据库中的测试用户名就是张三,而我们又指定了resultType是实体类User,MyBatis会通过反射获取到User类的信息,然后将查询出来的结果集中的每一列进行映射,通过调用这些属性的set方法绑定上值。如果查询不到或者映射不上,肯定是一个null。
(2)传入对象参数
传入对象参数,用于SQL语句中需要的参数比较多的情况下,比如插入记录的操作,可能需要插入多个字段,此时传入多个字段参数很明显不是一个好的解决方案,所以就需要把这些参数封装成一个对象进行操作,传入的时候只传入一个具体的对象,然后MyBatis会映射上这个对象所拥有的属性,我们在SQL语句中直接使用#{对象属性名}的方式就可以获取这些具体的值。举个例子,假设现在通过传入User对象的方式进行查询(在接下来会有插入的操作,其实都一样)。
在刚才的UserMapper.java接口中添加如下方法
public User getUserInfoByObj(User user); //通过传入实体类对象进行SQL查询
在UserMapper.xml中添加如下SQL映射块
<!--此时必须显式指定parameterType类型,即传入的参数类型是什么,这里就是我们的User类-->
<select id="getUserInfoByObj" resultType="User" parameterType="User">
SELECT * FROM UserInfo WHERE UserName=#{userName} <!--此处的userName是我们实体类中的变量名-->
</select>
同样,我们在服务层接口中定义同样的方法,在服务层实现类进行实现,即在UserServiceImpl中添加如下方法(前提是服务层接口要有这个定义的方法,博主因为减小篇幅省略了服务层接口,你不写接口也行,直接上服务层代码,个人习惯)。
@Override
public User getUserInfoByObj(User user) {
SqlSession sqlSession=MyBatisUtil.getSqlSession();
User user1=sqlSession.getMapper(UserMapper.class).getUserInfoByObj(user);
MyBatisUtil.close(sqlSession);
return user;
}
最后在测试类Test.java中对服务层进行调用
User userObj=new User();
userObj.setUserName("张三"); //设置上用户名,因为映射文件里是通过这个属性来查询的。查询用户名是张三的
User userSelect=new UserServiceImpl().getUserInfoByObj(userObj); //为了清晰,声明另一个User对象接收查询结果
System.out.println("通过传入对象参数的方式查询:"+userSelect.getUserName());
运行结果:
l
两种方式都可行,但是传入对象的方式一般用于插入记录的操作,这种查询情况只传一个用户名、或者ID即可,此处只是举个例子。
(3)传入Map参数
parameterType除了支持JavaBean实体类以外,还可传入Map类型的参数。此时在SQL语句中取值的时候依然是#{参数名},只不过此时的参数名指的是Map集合中的Key的名字,而取得的值则是这个Key对应的Value。很简单,上代码。
在UserMapper.java接口中定义如下方法
public User getUserInfoByMap(Map map); //通过传入Map集合的方式进行SQL查询
在UserMapper.xml中添加如下内容
<select id="getUserInfoByMap" resultType="User" parameterType="Map">
SELECT * FROM UserInfo WHERE UserName=#{keyName} <!--此处的keyName是我们传入的集合中的Key的名字-->
</select>
同样在服务层定义方法调用这个数据接口,然后在测试类调用服务层,服务层实现类添加如下代码
@Override
public User getUserInfoByMap(Map map) {
SqlSession sqlSession=MyBatisUtil.getSqlSession();
User user=sqlSession.getMapper(UserMapper.class).getUserInfoByMap(map);
MyBatisUtil.close(sqlSession);
return user;
}
测试类添加如下代码
Map map=new HashMap();
map.put("keyName","张三");
User userMap=new UserServiceImpl().getUserInfoByMap(map);
System.out.println("通过传入Map参数的方式查询:"+userMap.getUserName()); //你也可以不输出用户名,输出其他的字段
运行结果
二、使用resultMap实现简单的结果匹配
首先创建另一张数据库表RoleInfo(角色表),字段就是ID、RoleName,同时创建实体类Role。
简单的结果匹配其实就是为了解决实体类的属性名与数据库中的字段名不一致的问题。在上文我们要求POJO(即实体类)的属性名必须与数据库的字段名保持一致,才能完成自动映射。那么万一有不一样的属性名呢?比如在我们这两张表UserInfo和RoleInfo中,用户表中的UserRole列关联的是角色表中的ID,而不是直接关联的RoleName,那么将信息展示给用户的时候,用户关心的可不是ID而是角色名称(RoleName)。此时,我们先来一个比较low的做法,在实体类User中添加一个字段叫roleName,表示用户的角色名称。那么问题来了,用户表中可没有这个字段,MyBatis自动映射肯定是会报错的,因为它找不到这个属性,进而调不到这个属性的set方法。那么怎样才能映射上呢?肯定要通过两表联查获取角色名称了,然后在使用resultMap手动配置属性名与字段名的关联。在SQL映射标签中不再指定resultType属性,因为此时我们不是要直接返回实体类了,而是指定resultMap属性的值,指向一个resultMap的全局唯一id,在被调用的resultMap中指定返回的实体类。
resultMap常用的两个基本属性:
1、id:resultMap的唯一标识;
2、type:表示此resultMap映射的结果类型,通常是Java实体类。
在resultMap标签中,可使用<id/>和<result/>子标签指定一个属性名跟字段名的映射,property是实体类的属性名,column是数据库中的字段名。<id/>子节点一般对应该记录行的数据库主键字段,可提升MyBatis性能,二者作用差不多。
先说一下resultMap的自动映射级别:
PARTIAL,默认值,自动匹配基本类型
NONE,不进行自动匹配
FULL,自动匹配所有类型,包括复杂的JavaBean、引用类型。
一般设置为自动匹配所有,这样只需要在resultMap中配置名称不对应的属性即可,其他不做手动映射的属性依然可以映射上。此选项在mybatis-config.xml(核心配置文件)中的settings标签中设置,代码如下
<!--设置为FULL,自动匹配所有,则在resultMap中不进行匹配的字段也可以映射-->
<setting name="autoMappingBehavior" value="FULL"/>
接下来举个例子,在实体类User中增加String类型的属性roleName,在UserMapper.java接口中定义如下方法
public User getNameAndRole(@Param("uName")String userName); //查询用户信息同时获取角色名称
在UserMapper.xml中添加如下内容
<!--resultMap属性指向一个resultMap标签的id-->
<select id="getNameAndRole" resultMap="nameAndRole">
SELECT u.*,r.RoleName FROM UserInfo u,RoleInfo r WHERE UserName=#{uName} AND u.UserRole=r.ID
</select>
<!--type类型指定返回的实体类-->
<resultMap id="nameAndRole" type="User">
<id property="id" column="ID"/>
<result property="roleName" column="RoleName"/> <!--因为设置是映射级别为FULL,所以一些可以对应上的字段不必进行配置-->
</resultMap>
不再贴出服务层,列位自行补充实现吧,测试类添加如下代码
System.out.println("通过resultMap手动映射获取信息");
User userRole=new UserServiceImpl().getNameAndRole("张三"); //查询张三的
System.out.println(userRole.getUserName()+"的角色名称是:"+userRole.getRoleName());
System.out.println(user.getUserName()+"的编码是:"+userRole.getUserCode());
System.out.println(userRole.getUserName()+"的地址是:"+userRole.getUserAddress());
运行结果
这就是手动映射,简单的resultMap手动映射,只是映射与数据库字段名不匹配的属性名,其实还可以进行高级映射,在下文会介绍。可以看到,我们没有手动映射的属性,也可以成功的自动映射上值,因为我们设置了自动映射级别为FULL,否则的话,就要手动配置好多属性。
三、使用resultMap实现高级结果映射
在上文中,我们使用resultMap实现了简单的手动映射,处理实体类的属性与数据库字段不一致的情况。那么想一下,在实际的开发中,一个类所拥有的属性可能是复杂的,比如一个类拥有另一个类,或者拥有一个泛型集合,那么此时通过简单的映射已经无法实现,这时就引入resultMap的另一个子节点:association。association可以映射到Java实体类的某个复杂类型,比如一个类中嵌套另一个类。注意,association只能处理一对一的关联关系,不能处理一个类所包含的集合,处理集合用collection,用法跟association一样。association的使用方法很简单,只需要在resultMap标签中写上这个子标签,property代表实体类中的属性名,javaType代表这个属性实际要映射的类。接下来举个例子,在实体类User中添加一个Role类型的属性,属性名就叫role,在UserMapper.java接口中定义如下方法
public User getUserRole(@Param("uName")String userName); //高级映射,映射到类中的复杂数据类型
在UserMapper.xml中添加如下SQL映射块
<select id="getUserRole" resultMap="userAndRole">
SELECT u.*,r.RoleName,r.RoleCode FROM UserInfo u,RoleInfo r WHERE UserName=#{uName} AND u.UserRole=r.ID
</select>
<resultMap id="userAndRole" type="User">
<!--省略其他属性的映射-->
<association property="role" javaType="Role" resultMap="userRoleInfo"/> <!--映射User类中的实体类Role-->
</resultMap>
<!--此resultMap供association标签调用,写在外面有利于复用。返回Role类-->
<resultMap id="userRoleInfo" type="Role">
<!--省略其他属性的映射-->
<result property="roleName" column="RoleName"/>
</resultMap>
在服务层写好实现代码,调用数据层,服务层UserServiceImpl.java添加代码如下
@Override
public User getUserRole(@Param("uName") String userName) {
SqlSession sqlSession=MyBatisUtil.getSqlSession();
User user=sqlSession.getMapper(UserMapper.class).getUserRole(userName);
MyBatisUtil.close(sqlSession);
return user;
}
最后在测试类Test.java调用
System.out.println();
System.out.println("resultMap实现高级映射获取信息");
User user_gao=new UserServiceImpl().getUserRole("张三");
System.out.println("用户名称:"+user_gao.getUserName());
System.out.println("角色编号:"+user_gao.getRole().getRoleCode());
System.out.println("角色名称"+user_gao.getRole().getRoleName());
运行结果
可以看到,User类中的Role对象已经被映射上,我们是可以打印出来映射到的值的。而且因为我们设置了自动映射级别FULL,所以不管是User类还是Role类,虽然没有手动映射全部属性,但是这些属性还是都有值的(前提是必须跟数据库字段名可以对应,才可以省略)。在以上的示例中,association标签的property属性值就是“被包含的类在所包含类中的名称”,在这里就是role,就是User类中的role属性,而role的类型是Role,所以我们又指定javaType为Role,然后指向了一个外部的resultMap,这样更加灵活,可以达到某个resultMap的复用。当然也可以写在association里面,那就把association写成双标签,在里面直接定义<result/>标签,<result/>标签的使用都是一样的,不再阐述。
resultMap还有一个元素collection,用来处理一对多的关系,比如一个店铺有n张订单,在程序中可以表示为店铺类有一个订单类的集合。那么在查询店铺类的时候,采用两边联查,要映射上这个订单类集合,就要使用collection元素。它与association的用法简直一模一样,只不过多了一个指向ofType,说明你这个集合中放的是什么类型的,在这个例子中ofType就应该是订单类,订单类集合嘛。在这里博主不再建表测试,用法同association标签的用法,不再举出例子。
四、使用SQL映射文件实现增删改
1、增:<insert id='' parameterType=''>SQL语句</insert>
2、删:<delete id='' parameterType=''>SQL语句</delete>
3、改:<update id='' parameterType=''>SQL语句</update>
相比于查询,增删改就简便得多,因为它一般不需要去映射结果集,最多在传入实体类对象参数的时候做一个反射,去调用这个类中的属性。同查询一样,增删改都可直接传入字段参数、传入对象参数、传入Map类型参数,用法与上文一样,而且返回值类型都是整数型的,因为返回几行受影响,所以对于这些操作在DAO层定义接口的时候返回值类型最后是int整型。这些标签都没有resultType/resultMap属性,因为它们都是返回几行受影响,只有在查询的时候才需要这两个属性。
下面举例实现一下更新操作,更新一个用户的密码。
在UserMapper.java接口中定义如下方法
public int updatePwd(@Param("userId") int id, @Param("newPwd") String pwd); //修改密码
在UserMapper.xml中添加如下内容
<!--修改密码-->
<update id="updatePwd">
/*因为是使用注解的方式传入参数,可以不显式指定parameterType*/
UPDATE UserInfo SET UserPwd=#{newPwd} WHERE ID=#{userId};
</update>
在服务层UserServiceImpl实现如下代码
@Override
public int updatePwd(@Param("userId") int id, @Param("newPwd") String pwd) {
SqlSession sqlSession=MyBatisUtil.getSqlSession();
int num=sqlSession.getMapper(UserMapper.class).updatePwd(id,pwd);
sqlSession.commit();
MyBatisUtil.close(sqlSession);
return num;
}
在测试类中调用此服务
int num=new UserServiceImpl().updatePwd(1,"12345"); //将ID为1的用户,密码改为12345
if (num==1){
System.out.println("修改成功!");
}else {
System.out.println("系统异常!");
}
运行结果
是没有问题的。其余的新增操作、删除操作都一个道理,只不过映射标签不一样,SQL语句不一样,传参的类型、获得参数值的方式的一样的,不再一一举例。
学无止境,在错误中进步!