文章目录
1、综述
一般创建Web工程时,从数据库取数据的逻辑会放置在Dao层(Data Access Object,数据访问对象)。在之前的系列博客的实例中,我们发现:Dao的实现类其实并没有干什么实质性的工作,它仅仅就是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句,真正对DB进行操作的工作其实是由框架通过mapper中的SQL完成的。
因此在使用MyBatis开发Web工程时,通过Mapper动态代理机制,可以只编写数据交互的接口及方法定义,和对应的Mapper映射文件,具体的交互方法实现由MyBatis来完成, 这样大大节省了开发Dao层的时间。这种Dao的实现方式称为Mapper的动态代理方式。Mapper的动态代理方式无须程序员实现Dao接口,接口是由MyBatis结合映射文件自动生成的动态代理实现的。
2、实现Mapper动态代理
2.1 映射文件的namespace属性值
一般情况下,一个Dao接口的实现类方法使用的是同一个SQL映射id。所以,MyBatis框架要求,将映射文件中mapper标签的namespace属性设置为Dao接口的全类名, 则系统会根据方法所属的Dao接口,自动到相应namespace的映射文件中查找相关的SQL映射。
简单来说,通过接口名即可定位到SQL映射文件。
2.2 Dao接口方法名
MyBatis框架要求,接口中的方法名,与映射文件中相应的SQL标签的id值相同。 系统会自动根据方法名到相应的映射文件中查找同名的SQL映射id。
简单来说,通过方法名就可以定位到SQL映射文件中相应的SQL语句。
2.3 Dao对象的获取
在本博客之前的所有实例中,均是通过com.ccff.mybatis.datasource包下的DataConnection类中的getSqlSession方法来获取SqlSession对象,最终利用SqlSession对象的API方法实现对数据库的操作。
在使用Mapper动态代理后,需要用SqlSession对象的getMapper方法,通过动态代理的方式获取Mapper代理,通过动态代理的方式实现接口相应方法并调用使用。
2.4 删除Dao实现类
由于通过调用Dao接口方法,不仅可以从SQL映射文件中找到所要执行的SQL语句,还可以通过方法参数及返回值,将SQL语句的动态参数传入,将查询结果返回。所以,Dao的实现工作,完全可以由MyBatis系统自动根据映射文件完成。所以,Dao的实现类就不需要了。
Dao实现对象时由JDK的Proxy动态代理自动生成的。
3、测试案例
3.1 创建测试数据表
在mybaits数据库中创建名为“basketballplayer”的数据表,并添加测试数据。具体的建表语句与插入数据语句如下所示:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `basketballplayer`
-- ----------------------------
DROP TABLE IF EXISTS `basketballplayer`;
CREATE TABLE `basketballplayer` (
`id` int(25) NOT NULL auto_increment,
`name` varchar(255) default NULL,
`age` int(2) default NULL,
`weight` double(25,1) default NULL,
`height` int(25) default NULL,
`number` int(25) default NULL,
`team` varchar(255) default NULL,
`position` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of basketballplayer
-- ----------------------------
INSERT INTO `basketballplayer` VALUES ('1', 'LeBron James', '35', '113.4', '203', '23', 'LAL', 'F');
INSERT INTO `basketballplayer` VALUES ('2', 'Stephen Curry', '31', '86.2', '190', '30', 'WAS', 'G');
INSERT INTO `basketballplayer` VALUES ('3', 'Kevin Durant', '31', '108.8', '205', '35', 'WAS', 'F');
INSERT INTO `basketballplayer` VALUES ('4', 'James Harden', '30', '99.8', '195', '13', 'HOU', 'G');
INSERT INTO `basketballplayer` VALUES ('5', 'Chris Paul', '34', '79.4', '182', '3', 'HOU', 'G');
创建好以后的数据表如下所示:
3.2 创建实体类
在com.ccff.mybatis.model包下创建名为“BasketballPlayer”的实体类,并提供有参和无参构造方法、get和set方法以及toString方法。具体代码如下:
package com.ccff.mybatis.model;
public class BasketballPlayer {
private int id;
private String name;
private int age;
private Double weight;
private int height;
private int number;
private String team;
private String position;
public BasketballPlayer() {
}
public BasketballPlayer(String name, int age, Double weight, int height, int number, String team, String position) {
this.name = name;
this.age = age;
this.weight = weight;
this.height = height;
this.number = number;
this.team = team;
this.position = position;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getNumber() {
return number;
}
public void setNumber(int num) {
this.number = num;
}
public String getTeam() {
return team;
}
public void setTeam(String team) {
this.team = team;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
public String toString() {
return "BasketballPlayer{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
", height=" + height +
", number=" + number +
", team='" + team + '\'' +
", position='" + position + '\'' +
'}';
}
}
3.3 创建Dao文件
在com.ccff.mybatis.dao包下创建名为“IBasketballPlayerDao”的接口,在该接口中定义对篮球运动员的增删改查基础操作方法。具体代码如下:
package com.ccff.mybatis.dao;
import com.ccff.mybatis.model.BasketballPlayer;
public interface IBasketballPlayerDao{
//添加球员
void addBasketballPlayer(BasketballPlayer player);
//根据id删除球员
void deleteBasketballPlayerById(int id);
//根据id修改球员
void updateBasketballPlayerById(BasketballPlayer player);
//根据id查询球员
BasketballPlayer selectBasketballPlayerById(int id);
//查询所有球员
List<BasketballPlayer> selectAllasketballPlayer();
}
3.4 创建SQL映射文件
在config/sqlmap文件夹下创建名为“BasketballPlayerMapper”的XML文件,将mapper标签的namespace设置为IBasketballPlayerDao的全类名,并实现Mapper动态代理,具体代码如下:
<?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.ccff.mybatis.dao.IBasketballPlayerDao">
<!--增加球员-->
<insert id="addBasketballPlayer" parameterType="BasketballPlayer">
insert into basketballplayer(name,age,weight,height,number,team,position)
values (#{name},#{age},#{weight},#{height},#{number},#{team},#{position})
</insert>
<!--根据id删除球员-->
<delete id="deleteBasketballPlayerById" parameterType="BasketballPlayer">
delete from basketballplayer where id = #{id}
</delete>
<!--根据id修改球员-->
<update id="updateBasketballPlayerById" parameterType="BasketballPlayer">
update basketballplayer set height = #{height} where id = #{id}
</update>
<!--根据id查询球员-->
<select id="selectBasketballPlayerById" resultType="BasketballPlayer">
select * from basketballplayer where id = #{id}
</select>
<!--查询所有球员-->
<select id="selectAllasketballPlayer" resultType="BasketballPlayer">
select * from basketballplayer
</select>
</mapper>
将创建好的SQL映射文件配置到全局配置文件SqlMapConfig.xml中,具体配置如下:
<mappers>
<mapper resource="sqlmap/UserMapper.xml"/>
<mapper resource="sqlmap/StudentMapper.xml"/>
<mapper resource="sqlmap/BasketballPlayerMapper.xml"/>
</mappers>
3.5 修改数据源方法
在com.ccff.mybatis.datasource包下修改DataConnection类,在该类中添加名为“getBasketballPlayerMapper”的方法用于通过动态代理的方式获取IBasketballPlayerDao对象,具体代码如下:
public IBasketballPlayerDao getBasketballPlayerMapper() {
IBasketballPlayerDao basketballPlayerMapper = sqlSession.getMapper(IBasketballPlayerDao.class);
return basketballPlayerMapper;
}
3.6 创建测试类
在com.ccff.mybatis.test包下创建名为“BasketballPlayerTest”的测试类,具体代码如下:
package com.ccff.mybatis.test;
import com.ccff.mybatis.dao.IBasketballPlayerDao;
import com.ccff.mybatis.datasource.DataConnection;
import com.ccff.mybatis.model.BasketballPlayer;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class BasketballPlayerTest {
private IBasketballPlayerDao basketballPlayerMapper;
private SqlSession sqlSession;
public BasketballPlayerTest(){
super();
DataConnection dataConnection = new DataConnection();
try {
sqlSession = dataConnection.getSqlSession();
basketballPlayerMapper = dataConnection.getBasketballPlayerMapper();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void TestAddBasketballPlayer(){
System.out.println("插入球员前:");
TestSelectAllasketballPlayer();
BasketballPlayer player = new BasketballPlayer("Kawhi Leonard",28,104.3,200,2,"TRO","F");
basketballPlayerMapper.addBasketballPlayer(player);
sqlSession.commit();
sqlSession.close();
System.out.println("插入球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestDeleteBasketballPlayerById(){
System.out.println("删除球员前:");
TestSelectAllasketballPlayer();
basketballPlayerMapper.deleteBasketballPlayerById(5);
sqlSession.commit();
sqlSession.close();
System.out.println("删除球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestUpdateBasketballPlayerById(){
System.out.println("修改球员前:");
TestSelectAllasketballPlayer();
BasketballPlayer player = new BasketballPlayer();
BasketballPlayer basketballPlayer = null;
player.setId(3);
player.setHeight(213);
basketballPlayerMapper.updateBasketballPlayerById(player);
sqlSession.commit();
sqlSession.close();
System.out.println("修改球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestSelectBasketballPlayerById(){
BasketballPlayer player = basketballPlayerMapper.selectBasketballPlayerById(3);
sqlSession.close();
System.out.println(player);
}
@Test
public void TestSelectAllasketballPlayer(){
List<BasketballPlayer> players = basketballPlayerMapper.selectAllasketballPlayer();
for (BasketballPlayer player : players){
System.out.println(player);
}
sqlSession.close();
}
}
3.7 对球员的增删改查操作测试
运行添加球员的测试方法TestAddBasketballPlayer,得到如下结果说明测试正确。
运行删除球员的测试方法TestDeleteBasketballPlayerById,得到如下结果说明测试正确。
运行修改球员的测试方法TestUpdateBasketballPlayerById,得到如下结果说明测试正确。
运行查询球员的测试方法TestSelectBasketballPlayerById,得到如下结果说明测试正确。
运行查询全部球员的测试方法TestSelectAllasketballPlayer,得到如下结果说明测试正确。
4、注意
MyBatis框架对于Dao查询的自动实现,底层只会调用selectOne方法与selectList方法。 而框架选择方法的标准是测试类中用于接收返回值的对象类型。若接收类型为List,则自动选择selectList方法;否则,自动选择selectOne方法
5、解决多查询条件无法整体接收问题
在实际工作过程中,表单中所给出的查询条件有时是无法将其封装成一个对象的,也就是说,查询方法只能携带多个参数,而不能携带将这多个参数进行封装的一个对象。对于这个问题,有以下两种解决办法。
- 将多个参数封装成一个Map
- 逐个接收多个参数
5.1 修改接口Dao文件
修改接口Dao文件IBasketballPlayerDao,在接口中添加用于测试的方法,具体代码如下:
package com.ccff.mybatis.dao;
import com.ccff.mybatis.model.BasketballPlayer;
import java.util.List;
public interface IBasketballPlayerDao {
//添加球员
void addBasketballPlayer(BasketballPlayer player);
//根据id删除球员
void deleteBasketballPlayerById(int id);
//根据id修改球员
void updateBasketballPlayerById(BasketballPlayer player);
//根据id查询球员
BasketballPlayer selectBasketballPlayerById(int id);
//查询所有球员
List<BasketballPlayer> selectAllasketballPlayer();
//根据指定条件查询球员(演示多查询条件接收问题)
List<BasketballPlayer> selectBasketballPlayerByConditionByMap(Map<String,Object> map);
List<BasketballPlayer> selectBasketballPlayerByConditionByIndex(String name,int age);
}
5.2 修改SQL映射文件
修改接口SQL映射文件BasketballPlayerMapper.xml,在SQL映射文件中添加对应的SQL语句标签,具体配置如下:
<?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.ccff.mybatis.dao.IBasketballPlayerDao">
<!--增加球员-->
<insert id="addBasketballPlayer" parameterType="BasketballPlayer">
insert into basketballplayer(name,age,weight,height,number,team,position)
values (#{name},#{age},#{weight},#{height},#{number},#{team},#{position})
</insert>
<!--根据id删除球员-->
<delete id="deleteBasketballPlayerById" parameterType="BasketballPlayer">
delete from basketballplayer where id = #{id}
</delete>
<!--根据id修改球员-->
<update id="updateBasketballPlayerById" parameterType="BasketballPlayer">
update basketballplayer set height = #{height} where id = #{id}
</update>
<!--根据id查询球员-->
<select id="selectBasketballPlayerById" resultType="BasketballPlayer">
select * from basketballplayer where id = #{id}
</select>
<!--查询所有球员-->
<select id="selectAllasketballPlayer" resultType="BasketballPlayer">
select * from basketballplayer
</select>
<!--根据指定条件查询球员(演示多查询条件接收问题)-->
<select id="selectBasketballPlayerByConditionByMap" resultType="BasketballPlayer">
select * from basketballplayer where name like '%' #{nameCondition} '%' and age > #{ageCondition}
</select>
<select id="selectBasketballPlayerByConditionByIndex" resultType="BasketballPlayer">
select * from basketballplayer where name like '%' #{0} '%' and age > #{1}
</select>
</mapper>
通过SQL映射文件的配置可以看到,若通过将多个参数封装成一个Map的方式解决多查询参数问题,则#{}中填写的是Map的键名;若通过逐个接收参数的方式解决多查询参数问题,则#{}中填写的是每一个参数的下标,下标的参数时从0开始的。
5.3 修改测试类
修改接口SQL映射文件BasketballPlayerTest.xml,在测试类中添加名为“TestSelectBasketballPlayerByConditionByMap”和“TestSelectBasketballPlayerByConditionByIndex”的两个测试方法,具体配置如下:
package com.ccff.mybatis.test;
import com.ccff.mybatis.dao.IBasketballPlayerDao;
import com.ccff.mybatis.datasource.DataConnection;
import com.ccff.mybatis.model.BasketballPlayer;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BasketballPlayerTest {
private IBasketballPlayerDao basketballPlayerMapper;
private SqlSession sqlSession;
public BasketballPlayerTest(){
super();
DataConnection dataConnection = new DataConnection();
try {
sqlSession = dataConnection.getSqlSession();
basketballPlayerMapper = dataConnection.getBasketballPlayerMapper();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void TestAddBasketballPlayer(){
System.out.println("插入球员前:");
TestSelectAllasketballPlayer();
BasketballPlayer player = new BasketballPlayer("Kawhi Leonard",28,104.3,200,2,"TRO","F");
basketballPlayerMapper.addBasketballPlayer(player);
sqlSession.commit();
System.out.println("插入球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestDeleteBasketballPlayerById(){
System.out.println("删除球员前:");
TestSelectAllasketballPlayer();
basketballPlayerMapper.deleteBasketballPlayerById(5);
sqlSession.commit();
System.out.println("删除球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestUpdateBasketballPlayerById(){
System.out.println("修改球员前:");
TestSelectAllasketballPlayer();
BasketballPlayer player = new BasketballPlayer();
BasketballPlayer basketballPlayer = null;
player.setId(3);
player.setHeight(213);
basketballPlayerMapper.updateBasketballPlayerById(player);
sqlSession.commit();
System.out.println("修改球员后:");
TestSelectAllasketballPlayer();
}
@Test
public void TestSelectBasketballPlayerById(){
BasketballPlayer player = basketballPlayerMapper.selectBasketballPlayerById(3);
System.out.println(player);
}
@Test
public void TestSelectAllasketballPlayer(){
List<BasketballPlayer> players = basketballPlayerMapper.selectAllasketballPlayer();
for (BasketballPlayer player : players){
System.out.println(player);
}
}
@Test
public void TestSelectBasketballPlayerByConditionByMap(){
Map<String,Object> map = new HashMap<>();
map.put("nameCondition","en");
map.put("ageCondition",29);
List<BasketballPlayer> players = basketballPlayerMapper.selectBasketballPlayerByConditionByMap(map);
for (BasketballPlayer player : players){
System.out.println(player);
}
}
@Test
public void TestSelectBasketballPlayerByConditionByIndex(){
List<BasketballPlayer> players = basketballPlayerMapper.selectBasketballPlayerByConditionByIndex("en",29);
for (BasketballPlayer player : players){
System.out.println(player);
}
}
}
执行测试方法TestSelectBasketballPlayerByConditionByMap后,在控制台打印输出如下日志信息,说明测试通过。
执行测试方法TestSelectBasketballPlayerByConditionByIndex后,在控制台打印输出如下日志信息,说明测试通过。