02-MyBatis实现代理和传统方式开发
1 基于代理 Dao实现 CRUD 操作
使用要求:
1、持久层接口和持久层接口的映射配置必须在相同的包下
2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
3、SQL 语句的配置标签<select>,<insert>,<delete>,<update>的 id 属性必须和持久层接口的方法名相同。
1.1步骤
1、创建Maven工程
2、导入坐标
3、编写必要代码(实体类和持久层接口)
4、编写 SqlMapConfig.xml
5、编写映射文件
6、测试代码
提示:不会步骤的可以看第一章
1.2查询所有用户
在持久层接口加中添加 findAll方法 在 在持久层接口加中添加 findAll方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!--
select 标签用于定义一个select查询语句
resultType 属性定义返回值的数据类型
配置查询所有
-->
<!--查询所有-->
<select id="findAll" resultType="com.sixstar.mybatis.domain.User">
select * from user
</select>
</mapper>
在测试类添加测试
package com.sixstar.test;
import com.sixstar.mybatis.dao.IUserDao;
import com.sixstar.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.InputStream;
import java.util.List;
//测试mybatis的crud操作
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession();
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
//5.执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
}
1.3 保存操作
在持久层接口中添加新增方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 保存用户
* @param user
*/
void saveUser(User user);
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!--保存用户-->
<insert id="saveUser" parameterType="com.sixstar.mybatis.domain.User">
insert into user(username,address,sex,birthday) value(#{username},#{address},#{sex},#{birthday});
</insert>
</mapper>
细节:
parameterType 属性:
代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。
具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
它用的是 ognl 表达式。
ognl 表达式:
它是 apache 提供的一种表达式语言,全称是:
Object Graphic Navigation Language 对象图导航语言
它是按照一定的语法格式来获取数据的。
语法格式就是使用 #{对象.对象}的方式
#{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,
并调用 getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,
所以可以省略 user. 而直接写 username。
添加测试类中的测试方法
/**
* 测试保存操作
*/
@Test
public void testSave(){
User user = new User();
user.setUsername("modify User property");
user.setAddress("长沙");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:"+user);
//5.执行保存方法
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
这里需要注意打开 Mysql 数据库发现并没有添加任何记录,原因是什么?
这一点和 jdbc 是一样的,我们在实现增删改时一定要去控制事务的提交,那么在 mybatis 中如何控制事务 提交呢?
可以使用:session.commit();来实现事务提交。
1.4 用户更新
在持久层接口中添加更新方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 更新用户
* @param user
* @return 影响数据库记录的行数
*/
int updateUser(User user);
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.sixstar.mybatis.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},
address=#{address} where id=#{id}
</update>
</mapper>
加入更新的测试方法
/**
* 测试更新操作
*/
@Test
public void testUpdate(){
User user = new User();
user.setId(49);
user.setUsername("mybastis update user");
user.setAddress("北京市顺义区");
user.setSex("女");
user.setBirthday(new Date());
//5.执行保存方法
userDao.updateUser(user);
}
1.5 用户删除
在持久层接口中添加删除方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 根据Id删除用户
* @param userId
*/
void deleteUser(Integer userId);
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>
</mapper>
加入删除的测试方法
/**
* 测试删除操作
*/
@Test
public void testDelete(){
//5.执行删除方法
userDao.deleteUser(48);
}
1.6 根据ID查询
在持久层接口加中添加findById方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 根据id查询用户 -->
<select id="findById" parameterType="int" resultType="com.sixstar.mybatis.domain.User">
select * from user where id = #{uid}
</select>
</mapper>
在测试类添加测试
/**
* 测试根据id查询操作
*/
@Test
public void testFindOne(){
//5.执行查询一个方法
User user = userDao.findById(49);
System.out.println(user);
}
1.7 用户模糊查询
在持久层接口中添加模糊查询方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.sixstar.mybatis.domain.User">
select * from user where username like #{name}
</select>
</mapper>
加入模糊查询的测试方法
/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
//5.执行查询一个方法
List<User> users = userDao.findByName("%王%");
for(User user : users){
System.out.println(user);
}
}
我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标 识%。配置文件中的#{username}也只是一个占位符,所以 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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 根据名称模糊查询 -->
<select id="findByName" parameterType="string" resultType="com.sixstar.mybatis.domain.User">
select * from user where username like '%${value}%'
</select>
</mapper>
我们在上面将原来的#{}占位符,改成了{value}的写 法就是固定的,不能写成其它名字。
可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。
#{} 与${} 区别
#{} 表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类
型值,#{}括号中可以是 value 或其它名称。
${} 表示拼接 sql 串
通过${}可以将 parameterType 传入的内容拼接在 sql中且不进行 jdbc 类型转换, ${}可以接收简
单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
模糊查询的${value}源码分析
这就说明了源码中指定了读取的 key 的名字就是”value”,所以我们在绑定参数时就只能叫 value 的名字了。
1.8 查询使用聚合函数
在持久层接口中添加模糊查询方法
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 查询总记录条数
* @return
*/
int findTotal();
}
在用户的映射配置文件中配置
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 获取用户的总记录条数 -->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
加入聚合查询的测试方法
/**
* 测试查询总记录条数
*/
@Test
public void testFindTotal(){
//5.执行查询一个方法
int count = userDao.findTotal();
System.out.println(count);
}
1.9 细节问题
问题扩展:新增用户 id 的返回值
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!--保存用户-->
<insert id="saveUser" parameterType="com.sixstar.mybatis.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday) value(#{username},#{address},#{sex},#{birthday});
</insert>
</mapper>
新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相 当于我们要在新增后将自动增长 auto_increment 的值返回。
2 Mybatis 的参数深入
2.1 parameterType 配置
使用说明
我们在上一章节中已经介绍了 SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以
是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装
类,本章节将介绍如何使用实体类的包装类作为参数传递。
注意事项
基本类型和 String 我们可以直接写类型名称 ,也可以使用包名.类名的方式,例如 :java.lang.String。
实体类类型,目前我们只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,
而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天的最后一个章节中将讲解如何注册实体类
的别名。
在 mybatis 的官方文档的说明(第 19 页)
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。
可以参考 TypeAliasRegistery.class 的源码。
2.2 传递 pojo 包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查
询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
编写 QueryVo
package com.sixstar.mybatis.domain;
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
编写持久层接口
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 根据 QueryVo 中的条件查询用户
* @param vo
* @return
*/
List<User> findByVo(QueryVo vo);
}
持久层接口的映射文件
<?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">
<!--
namespace属性是名称空间的意思。
功能相当于 给配置文件定义一个包名。
一般情况下。可以写两种值,一种是对应类的全类名
一种情况是。对应类的处理接口全类名(后面讲)。
-->
<mapper namespace="com.sixstar.mybatis.dao.IUserDao">
<!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 -->
<select id="findByVo" resultType="com.sixstar.mybatis.domain.User" parameterType="com.sixstar.mybatis.domain.QueryVo">
select * from user where username like #{user.username};
</select>
</mapper>
测试包装类作为参数
/**
* 测试使用QueryVo作为查询条件
*/
@Test
public void testFindByVo(){
QueryVo vo = new QueryVo();
User user = new User();
user.setUsername("%王%");
vo.setUser(user);
//5.执行查询一个方法
List<User> users = userDao.findByVo(vo);
for(User u : users){
System.out.println(u);
}
}
3 Mybatis 的输出结果封装
3.1 resultType 配置结果类型
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
我们在前面的 CRUD 案例中已经对此属性进行过应用了。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须
使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)
同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法
实现封装。
3.2 特殊情况示例
修改实体类
package com.sixstar.mybatis.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + ''' +
", userBirthday=" + userBirthday +
", userSex='" + userSex + ''' +
", userAddress='" + userAddress + ''' +
'}';
}
}
修改测试类
package com.sixstar.test;
import com.sixstar.mybatis.dao.IUserDao;
import com.sixstar.mybatis.domain.QueryVo;
import com.sixstar.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.InputStream;
import java.util.Date;
import java.util.List;
//测试mybatis的crud操作
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession();
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 测试保存操作
*/
@Test
public void testSave(){
User user = new User();
user.setUserName("modify insert property_2");
user.setUserAddress("长沙");
user.setUserSex("男");
user.setUserBirthday(new Date());
System.out.println("保存操作之前:"+user);
//5.执行保存方法
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
/**
* 测试更新操作
*/
@Test
public void testUpdate(){
User user = new User();
user.setUserId(49);
user.setUserName("mybastis update user");
user.setUserAddress("北京市顺义区");
user.setUserSex("女");
user.setUserBirthday(new Date());
//5.执行保存方法
userDao.updateUser(user);
}
/**
* 测试使用QueryVo作为查询条件
*/
@Test
public void testFindByVo(){
QueryVo vo = new QueryVo();
User user = new User();
user.setUserName("%王%");
vo.setUser(user);
//5.执行查询一个方法
List<User> users = userDao.findByVo(vo);
for(User u : users){
System.out.println(u);
}
}
}
修改映射文件
<!--保存用户-->
<insert id="saveUser" parameterType="com.sixstar.mybatis.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="userId" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday) value(#{userName},#{userAddress},#{userSex},#{userBirthday});
</insert>
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.sixstar.mybatis.domain.User">
update user set username=#{userName},birthday=#{userBirthday},sex=#{userSex},
address=#{userAddress} where id=#{userId}
</update>
测试查询结果
为什么名称会有值呢? 因为:mysql 在 windows 系统中不区分大小写!
修改映射配置
<!--查询所有-->
<select id="findAll" resultType="com.sixstar.mybatis.domain.User">
<!-- select id as userId,username as userName,birthday as userBirthday,
sex as userSex,address as userAddress from user-->
select * from user
</select>
思考:
如果我们的查询很多,都使用别名的话写起来岂不是很麻烦,有没有别的解决办法呢?
3.3 resultMap 结果类型
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类
型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
在映射文件中 定义 resultMap
<!--
建立 User 实体和数据库表的对应关系
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="com.sixstar.mybatis.domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
修改映射配置
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
4 Mybatis 传统 DAO 层开发[ [ 了解] ]
使用 Mybatis 开发 Dao,通常有两个方法,即原始 Dao开发方式和 Mapper 接口代理开发方式。而现在主流
的开发方式是接口代理开发方式,这种方式总体上更加简便。我们的讲解也主要以接口代理开发方式为主。在
第一章节已经给大家介绍了基于代理方式的 dao 开发,现在给大家介绍一下基于传统编写 Dao 实现类的开发方式。
4.1 Mybatis 实现 DAO 的传统开发方式
步骤
1、创建maven工程
2、复制上一个工程的内容
3、删除QueryVo实体
4、修改映射文件
4.1、删除resultMap配置
4.2、修改查询所有resultMap属性为resultType
4.3、修改增加修改里的属性为之前那个
<!--保存用户-->
<insert id="saveUser" parameterType="com.sixstar.mybatis.domain.User">
<!-- 配置插入操作后,获取插入数据的id -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday) value(#{username},#{address},#{sex},#{birthday});
</insert>
<!-- 更新用户 -->
<update id="updateUser" parameterType="com.sixstar.mybatis.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},
address=#{address} where id=#{id}
</update>
5、修改实体类
package com.sixstar.mybatis.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
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 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;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", birthday=" + birthday
+ ", sex=" + sex + ", address="
+ address + "]";
}
}
持久层 Dao 接口
package com.sixstar.mybatis.dao;
import com.sixstar.mybatis.domain.User;
import java.util.List;
//用户的持久层接口
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 保存用户
* @param user
*/
void saveUser(User user);
/**
* 更新用户
* @param user
* @return 影响数据库记录的行数
*/
int updateUser(User user);
/**
* 根据Id删除用户
* @param userId
*/
void deleteUser(Integer userId);
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 根据名称模糊查询用户信息
* @param username
* @return
*/
List<User> findByName(String username);
/**
* 查询总记录条数
* @return
*/
int findTotal();
}
持久层 Dao 实现类
package com.sixstar.mybatis.dao.impl;
import com.sixstar.mybatis.dao.IUserDao;
import com.sixstar.mybatis.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
public class UserDaoImpl implements IUserDao {
//定义一个SqlSessionFactory工厂
private SqlSessionFactory factory;
//创建构造方法给SqlSessionFactory工厂赋值
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public List<User> findAll() {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询列表
//参数就是能获取配置信息的key
List<User> users = session.selectList("com.sixstar.mybatis.dao.IUserDao.findAll");
//3.释放资源
session.close();
return users;
}
@Override
public void saveUser(User user) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现保存
session.insert("com.sixstar.mybatis.dao.IUserDao.saveUser",user);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public void updateUser(User user) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现更新
session.update("com.sixstar.mybatis.dao.IUserDao.updateUser",user);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public void deleteUser(Integer userId) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用方法实现删除
session.update("com.sixstar.mybatis.dao.IUserDao.deleteUser",userId);
//3.提交事务
session.commit();
//4.释放资源
session.close();
}
@Override
public User findById(Integer userId) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询一个
User user = session.selectOne("com.sixstar.mybatis.dao.IUserDao.findById",userId);
//3.释放资源
session.close();
return user;
}
@Override
public List<User> findByName(String username) {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("com.sixstar.mybatis.dao.IUserDao.findByName",username);
//3.释放资源
session.close();
return users;
}
@Override
public int findTotal() {
//1.根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2.调用SqlSession中的方法,实现查询一个
Integer count = session.selectOne("com.sixstar.mybatis.dao.IUserDao.findTotal");
//3.释放资源
session.close();
return count;
}
}
测试类
package com.sixstar.test;
import com.sixstar.mybatis.dao.IUserDao;
import com.sixstar.mybatis.dao.impl.UserDaoImpl;
import com.sixstar.mybatis.domain.User;
import org.apache.ibatis.io.Resources;
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.InputStream;
import java.util.Date;
import java.util.List;
/**
*
* 测试mybatis的crud操作
*/
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用工厂对象,创建dao对象
userDao = new UserDaoImpl(factory);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//6.释放资源
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
//5.执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
/**
* 测试保存操作
*/
@Test
public void testSave(){
User user = new User();
user.setUsername("dao impl user");
user.setAddress("北京市顺义区");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("保存操作之前:"+user);
//5.执行保存方法
userDao.saveUser(user);
System.out.println("保存操作之后:"+user);
}
/**
* 测试更新操作
*/
@Test
public void testUpdate(){
User user = new User();
user.setId(50);
user.setUsername("userdaoimpl update user");
user.setAddress("北京");
user.setSex("女");
user.setBirthday(new Date());
//5.执行保存方法
userDao.updateUser(user);
}
/**
* 测试删除操作
*/
@Test
public void testDelete(){
//5.执行删除方法
userDao.deleteUser(54);
}
/**
* 测试删除操作
*/
@Test
public void testFindOne(){
//5.执行查询一个方法
User user = userDao.findById(50);
System.out.println(user);
}
/**
* 测试模糊查询操作
*/
@Test
public void testFindByName(){
//5.执行查询一个方法
List<User> users = userDao.findByName("%王%");
for(User user : users){
System.out.println(user);
}
}
/**
* 测试查询总记录条数
*/
@Test
public void testFindTotal(){
//5.执行查询一个方法
int count = userDao.findTotal();
System.out.println(count);
}
}
4.2 Mybatis执行流程分析
断点调试
1.1在测试类的查询所有方法打个断点
List users = userDao.findAll();
1.2在接口实现类的查询所有方法打个断点
SqlSession session = factory.openSession();
经过调试发现他的实现类是DefaultSqlSession
查看DefaultSqlSession
1、进入到SqlSession点击右键,然后如图展示
2、选中SqlSession点击右键,如图展示
3、选中实现类双击上面框起来的DefaultSqlSession,进入到源码中
4、找到selectList方法
在这里我们可以很清楚的发现,我们用的是传入一个参数的方法,然后第一个方法调用了第二个方法,第二个方法又调用了第三个方法,接着第三个方法返回了一个query方法,那么我们继续往下面看。
5、我们发现进入到query方法又是一个接口,下面继续断点调试查找他的实现类,在接口类的query方法上面打一个断点,找到它的实现类
经过断点调试找到它的实现类我们发现,它是直接返回最后一行的delegat query方法,那么下面我们通过断点的方式发现delegate是SimpleExecutor
6、进入到SimpleExecutor发现,只有一个doQuery的方法
到这一步他们之间肯定是做了一个转换的操作,可以通过继承的BaseExecutor找到query方法
7、进入到BaseExecutor,发现有query方法
而BaseExecutor有的话,它在调用这个query,跟进去找到queryFromDatabase,点到queryFromDatabase方法,你会发现在这个里面就有doQuery方法,然后再跟进去发现doQuery是一个抽象方法。
8、我们回到BaseExecutor的子类,也就是在这个类我们重写了doQuery方法,并且它调用了一个handler.query方法,然后可以继续在doQuery方法打个断点,通过断点发现handler是RoutingStatementHandler。
9、进入到RoutingStatementHandler找到query方法打个断点。
到这里来了我们就会发现,它返回的又是一个delegat query方法,不过他们的实现类不同,这个delegat query方法的实现类,是PreparedStatementHandler。
10、进入到PreparedStatementHandler类找到query方法,然后继续打断点调试,
到这一步就可以发现,我们已经找到了想要的内容了,这个不就是我们jdbc的PreparedStatement吗,只不过它是一个代理对象,它就有一个方法ps.execute();,大家都知道ps.execute();方法不止只能执行查询,还可以执行增删改,我们在这是用它执行了一个查询方法,再往下走一步到resultSetHandler. handleResultSets。
11、我们进入到BaseStatementHandler,发现这是一个抽象类,找到ResultSetHandler这个接口,通过接口找到他的实现类DefaultResultSetHandler。
12、进入到DefaultResultSetHandler类中,这个类里面就做了一件事就是调用handleResultSets在这个方法里面它调用了handleResultSet。
这个里面就是它在给我们封装结果集了。
13、进入到handleResultSet方法,在这个里面,它做的事情就是赋值和取值了
总结
1、 selectList方法先通过statement字符串从configuration中获取对应的MappedStatement实例,再调用executor的query()方法;
2、 query()方法先根据MappedStatement等参数创建相应的缓存索引cacheKey,再调用query()方法执行查询;
3、 从缓存cache中查询是否存在要查询的数据,如果不存在调用query进行查询;
4、 该query()方法位于SimpleExecutor的父类BaseExecutor类中,主要调用queryFromDatabase()方法;
5、 调用doQuery()方法执行查询操作,并把查询结果放入localCache()中;
6、 根据MappedStatement创建相应的prepareStatement,具体的创建过程和update中的类似,都是先获取数据库连接connection,通过数据库连接获取对应的preparementStatement实例;获取statement实例后执行query查询操作;
7、 创建好statement后执行相应的query操作;
8、调用handleResultSets()方法对查询得到的结果集进行处理;
从statement中获取结果集resultSet,并将其包装成ResultSetWrapper
根据statement获取对应的resultMap,默认都会存在一个resultMap,resultType定义的也存在一个resultMap
对结果进行校验,如果有结果返回但是没有resultType或者resultMap与之对应的话抛出异常;
5 SqlMapConfig.xml配置
5.1 配置内容
SqlMapConfig.xml 中配置的内容和顺序
-properties (属性)
--property
-settings(全局配置参数)
--setting
-typeAliases (类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers (映射器)
--mapper
--package
5.2 properties (属性)
在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。
第一种
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/sixstar"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="1234"/>
</properties>
第二种
在classpath 下定义 db.properties 文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=123
6.2.2.2 properties 标签配置
<!-- 配置properties
可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
resource属性: 常用的
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
url属性:
是要求按照Url的写法来写地址
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
它的写法:
http://localhost:8080/mybatisserver/demo1Servlet
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
-->
<properties url="file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
</properties>
此时我们的 dataSource 标签就变成了引用上面的配置
<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>
5.3 typeAliases
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
自定义别名:
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就再区分大小写<typeAlias type="com.sixstar.mybatis.domain.User" alias="user"></typeAlias>-->
<!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.sixstar.mybatis.domain"></package>
</typeAliases>
5.4 mappers (映射器)
mapper resource=" " /
使用相对于类路径的资源
如:<mapper resource="com/sixstar/mybatis/dao/IUserDao.xml"></mapper>
mapper class=" " /
使用 mapper 接口类路径
如:<mapper class="com.sixstar.mybatis.dao.UserDao"/>
注意:此种方法要求mapper接口名称和mapper 映射文件名称相同,且放在同一个目录中。
package name=""/
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<!-- 配置映射文件的位置 -->
<mappers>
<!--<mapper resource="com/sixstar/mybatis/dao/IUserDao.xml"></mapper>-->
<!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class了 -->
<package name="com.sixstar.mybatis.dao"></package>
</mappers>