Mybatis系列文章
Mybatis | 03 Mybatis的两种使用方式
1. Mybatis实现DAO的代理开发
1.1 相关准备
1.1.1 持久层Dao接口
package org.example.dao;
public interface IUserDao{
//保存操作
void save(User user);
//更新操作
void update(User user);
//删除操作
void delete(Integer userId);
//不同的查询操作
//查询所有
List<User> findAll();
//通过ID精确查询
User findById(Integer userId);
//通过名称模糊查询
List<User> findByName(String username);
//使用聚合函数查询
Integer findTotal();
//使用综合条件查询
List<User> findByVo(QueryVo vo);
}
1.1.2 用户实体类
package org.example.domain;
public class User{
private Integer id;
private String username;
private Date birthday;
private Integer bonus;
//省略了所有的get和set方法
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", bonus=" + bonus +
'}';
}
}
1.1.3 测试方法
- 基本使用(以findAll方法为例)
//Mybatis的运行过程,
@Test
public void test() throws Exception{
//读取配置文件
//会有异常要抛一个异常
InputStream in = Resources.getResourceAsStream(IUserDao.class);
//使用SqlSessionFactoryBuilder创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build();
//使用SqlSessionFactory生产获取SqlSession对象
SqlSession session = factory.openSession();
//创建Dao的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//使用代理对象执行方法
List<User> users = userDao.findAll();
//后续处理省略
//提交修改
session.commit();
//释放资源
session.close();
in.close();
}
- 抽取重复代码后
//所要使用到的对象
private InputStream in = null;
private SqlSessionFactory factory = null;
private SqlSession session = null;
private IUserDao userDao = null;
@Before
//在测试方法之前执行,初始化以上的对象
public void init() throws Exception{
in = Resources.getResourceAsStream(IUserDao.class);
factory = new SqlSessionFactoryBuilder().build();
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}
@After
//在测试方法之后执行,提交并释放资源
public void destroy() throws Exception{
session.commit();
session.close();
in.close();
}
@Test
public void findAllTest(){
List<User> users = userDao.findAll();
}
Tips 1.1:手动提交
AutoCommit被设置成了false,因此在执行DML语句后要手动提交否则就会回滚
- 没有提交
- 运行代码
- 运行结果
- 提交
- 运行代码
- 运行结果
1.2 实现CRUD操作(Most Important)
1.2.1 保存操作Create
<!--保存操作-->
<insert id="saveUser" parameterType="org.example.domain.User">
insert into user(username,birthday,bonus)
values(#{username},#{birthday},#{bonus});
</insert>
Tips 1.2:获取新增用户的ID
数据库提供的==方法 last_insert_id( )==可以返回新增记录的ID
- 在保存操作中增加 selectKey标签 配置
<insert id="saveUser" parameterType="org.example.domain.User">
<!--在保存后获取新增用户的ID-->
<!--keyProperty代表要返回的值对应实体类中属性的名称-->
<!--keyColumn代表数据库中列的名称
<!--order指定操作是插入操作前BEFORE还是插入操作后AFTER执行-->
<selectKey keyProperty="id" keyColumn="id" resultType="INT" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,bonus)
values(#{username},#{birthday},#{bonus});
</insert>
标签属性:
- keyProperty属性 指定要返回的值对应实体类中的属性名称
- keyColumn属性 指定返回数据库中列的名称
- order属性 指定操作是插入操作前BEFORE还是插入操作后AFTER执行
- 测试
- 没有获取ID操作
@Test
public void saveUserTest(){
User user = new User();
user.setUserName("abc");
user.setUserBirthday(new Date());
user.setUserBonus(1000);
userDao.saveUser(user);
System.out.println(user);
}
运行结果
- 有获取ID操作
@Test
public void saveTest() {
User user = new User();
user.setUserName("abc");
user.setUserBirthday(new Date());
user.setUserBonus(1000);
System.out.println("before:"+user);
userDao.saveUser(user);
System.out.println("after:"+user);
}
运行结果
1.2.2 删除操作Delete
<!--删除操作-->
<delete id="deleteUser" parameterTyoe="int/Integer/java.lang.Integer">
delete from user weher id=#{id};
</delete>
1.2.3 更新操作Update
<!--更新操作-->
<update id="updateUser" parameterType="org.example.domain.User">
update user set username=#{username},birthday=#{birthday},bonus=#{bouns}
where id=#{id};
</update>
1.2.4 查询操作Retrieve
1.2.4.1 查询所有
<!--查询所有-->
<select id="findAll" resultType="org.example.domain.User">
select * from user;
</select>
1.2.4.2 通过ID精确查询
<!--通过ID精确查询-->
<select id="findById" parameterType="int" resultType="org.example.domain.User">
select * from user where id=#{id};
</select>
1.2.4.3 通过名称模糊查询
<!--通过名称模糊查询-->
<select id="fingByName" parameterType="string" resultType="org.example.domain.User">
select * from user where username like #{name}
select * from user where username like '%${Value}%'
</select>
Tips 1.3:两种模糊查询方式
- 结果对比
- 使用#{ }的模糊查询
- 使用${ }的模糊查询
- 分析:#{}和${}的对比
-
#{ }表示一个占位符,将传入的值赋值给PreparedStatement中的占位符
可以接收基本类型或pojo类型值,当为基本类型时#{ }括号中可以为任意名称
<!--username为string类型数据,所以一下两条语句是等价的-->
select * from user where username like #{username};
select * from user where username like #{name};
-
${ }表示拼接SQL字符串,将传入的值直接拼接在SQL中
可以接收基本类型或pojo类型值,当为基本类型是${ }括号中只能为value
<!--这里就只能是value-->
select * from user where username like '%${value}%';
- 结论:更多使用#{ }占位符的形式,基于PreparedStatement安全性高
1.2.4.4 使用聚合函数查询
<!--使用聚合函数查询-->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
1.2.4.5 使用综合条件查询
联系2.3.2
<!--使用综合条件查询-->
<select id="fingByVo" parameterType="org.example.domain.QueryVo"
resultType="org.example.domain.User">
select * from user where username like #{user.username}
</select>
1.3 CRUD标签属性
1.3.1 常用属性
虽然对于不同的操作使用不同的标签但是标签中的属性基本相同
示例
<!--通过ID精确查询-->
<select id="findById" parameterType="int" resultType="org.example.domain.User">
select * from user where id=#{id};
</select>
-
使用id属性确定方法名,可以和mapper标签中的namespace一起定位方法
-
使用value属性(位于两个标签之间的部分)指定是要执行的SQL语句,
对于可变参数在写SQL语句时使用==#{属性}==占位符代替
-
使用parameterType属性指定输入参数的类型,以便给可变参数赋值
-
使用resultType属性指定返回值封装的类型
1.3.2 输入参数parameterType深入
-
可以传递简单类型
-
可以传递pojo对象
-
可以传递pojo包装对象:有多个对象共同组合成查询的条件
pojo包装对象:将各个查询条件封装到QueryVo中作为属性合成一个综合的查询条件
//将各个查询条件封装到QueryVo中作为属性
package org.example.domain;
public class QueryVo{
private User user;
private OtherType other;
public void setUser(User user){
this.user = user;
}
public User getUser(){
return user;
}
public void setOther(OtherType other){
this.other = other;
}
public OtherType getOther(){
return other;
}
}
Tips 1.4:补充OGNL表达式
mybatis在配置时使用的就是OGNL表达式
Object Graphic Navigation Language:对象图导航语言
功能:通过对象的取值方法来获取数据,在写法上省略了get
- 一般的写法:user.getAttribute( )
- OGNL的写法:user.attribute
问题:在mybatis中直接使用"username而"不用"user."
原因:已经在ParameterType提供了属性所属的类
Tips 1.5:补充pojo对象
Plain Ordinary Java Object:简单的、普通的Java对象
内在含义:那些没有继承任何类、也没有实现任何接口,更没有被其它框架侵入的java对象
可以看做是作为支持业务逻辑或持久化逻辑的协助类
1.3.3 返回值封装深入
尽量保证一致可以减少不必要的麻烦
1.3.3.1 实体类中的属性名和数据库中的列名相同
resultType属性:支持基本类型和实体类类型结果集的封装
要求:实体类中的属性名和数据库中的列名必须相同
1.3.3.2 实体类中的属性名和数据库中的列名不相同
- 更改后的用户实体类
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private Integer userBonus;
//省略了所有的get和set方法
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userBirthday=" + userBirthday +
", userBonus=" + userBonus +
'}';
}
}
- 对于增删改操作而言要修改与实体类属性名有关的位置
主要包括:keyProperty、SQL语句占位符中的属性名
修改之前:
<insert id="saveUser" parameterType="org.example.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="INT" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,bonus)
values(#{username},#{birthday},#{bonus});
</insert>
修改之后:
<insert id="saveUser" parameterType="org.example.domain.User">
<selectKey keyProperty="userId" keyColumn="id" resultType="INT" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,bonus)
values(#{userName},#{userBirthday},#{userBonus});
</insert>
- 对于查询操作有更大的问题
若仍使用resultType查询结果无法对应就无法封装相应的值
解决方案
- 在数据库的层面解决:给列名起别名
优劣势:执行效率高,但当查询语句较多时修改语句工作量大
<select id="findAll" resultMap="userMap">
select id as userId,username as userName,birthday as userBirthday,bonus as userBonus
from user;
</select>
- 使用MyBatis提供的解决方案resultMap标签:配置结果列名和实体类属性的对应关系
标签属性:
- id属性给当前的resultMap一个唯一标识,在select标签中引用
- type属性指定实体类的全限定类名
子标签:
- id标签用于指定主键对应的字段
- result标签用于指定非主键对应的字段
子标签属性:
- property属性用于指定实体类属性的名称
- column属性用于指定数据库的列名
优劣势:执行效率不高,但配置完成后所有的查询语句都可以使用便于开发
<!--配置实体类和列名的对应关系-->
<resultMap id="userMap" type="com.yhn.domain.User">
<!--主键对应字段-->
<id property="userId" column="id"></id>
<!--非主键-->
<result property="userName" column="username"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userBonus" column="bonus"></result>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user;
</select>
2. Mybatis实现DAO的传统开发
2.1 实现CRUD操作
- 配置文件
不再使用代理对象而是自己编写实现类,但还是要和代理一样编写相同的配置文件
- 持久层DAO接口
package org.example.dao;
public interface IUserDao{
//保存操作
void saveUser(User user);
//更新操作
void updateUser(User user);
//删除操作
void deleteUser(Integer userId);
//不同的查询操作
//查询所有
List<User> findAll();
//通过ID精确查询
User findById(Integer userId);
//通过名称模糊查询
List<User> findByName(String username);
//使用聚合函数查询
Integer findTotal();
}
- DAO接口实现类
package org.example.dao.impl;
public class UserDaoImpl implements IUserDao{
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
//参数就是能够获取配置文件信息的key
public List<User> findAll(){
SqlSession session = factory.openSession();
List<User> users = session.selectList("org.example.dao.IUserDao.findALL");
session.close();
return users;
}
public User findById(Integer userId){
SqlSession session = factory.openSession();
User user = session.selectOne("org.example.dao.IUserDao.findById",userId);
session.close();
return user;
}
public void saveUser(User user){
SqlSession session = factory.openSession();
session.insert("org.example.dao.IUserDao.saveUser",user);
session.commit();
session.close();
}
public void updateUser(User user){
SqlSession session = factory.openSession();
session.update("org.example.dao.IUserDao.updateUser",user);
session.commit();
session.close();
}
public void deleteUser(Integer userId){
SqlSession session = factory.openSession();
session.delete("org.example.dao.IUserDao.deleteUser",userId);
session.commit();
session.close();
}
//剩余的查询方法省略..
}
Tips 2.1 :传统开发方式的分析
-
编写实现类的方式调用了SqlSession类中提供的方法来完成CRUD操作
如:selectList( )、selectOne( )、insert( )、update( )、delete( )
-
方法中的参数就是能获取配置文件信息的键值,即配置文件中的namespace属性+id属性
- 测试方法
//所要使用到的对象
private InputStream in = null;
private SqlSessionFactory factory = null;
//已经在实现类中定义了
//private SqlSession session = null;
private IUserDao userDao = null;
@Before
//在测试方法之前执行,初始化以上的对象
public void init() throws Exception{
in = Resources.getResourceAsStream(IUserDao.class);
factory = new SqlSessionFactory().build(in);
userDao = UserDaoImpl(factory);
}
@After
//在测试方法之后执行,提交并释放资源
public void destroy() throws Exception{
in.close();
}
@Test
public void findAllTest(){
List<User> users = userDao.findAll();
}
2.2 与代理开发的对比
-
传统实现类的方式仍然要编写配置文件,同时还要自己编写实现类
-
而代理开发只需要编写配置文件,实现方式在获取Mapper接口后由框架完成,更便于开发
结论:因此更多的使用代理的方式完成CRUD的操作
2.3 后续
这种编写实现类的方式可以更好地去分析mybatis的源码进而了解其工作原理,后续部分再做记录