SSM框架系列学习总结5之Mybatis实现基本CRUD和代理开
首先, 回顾Mybatis的入门使用方法:
- 创建Java工程
-
导入相关jar包
jar.png
其中, 需要数据库驱动包, Mybatis核心包, 包括JUnit单元测试所需的jar包 hamcrest-core
和junit-4.12
日志格式输出的jar包 log4j
和commons-logging
- 准备Mybatis的全局配置文件
具体内容见上一篇整理的博客! - 准备Mybatis的映射文件
在映射文件中编写sql语句
<!--在映射文件中编写sql语句-->
<insert></insert>
<update></update>
<delete></delete>
<select id="" parameterType="" resultType=""></select>
<!--
id: statement的id
ParmeterType: 执行sql语句输入参数的类型
ResultType: 输出的参数类型
-->
单元测试程序:
![](https://img.haomeiwen.com/i2987011/bb7617829da788fc.png)
SqlSession对象的获取和使用:
SqlSessionFactoryBuilder ---> SqlSessionFactory ---> SqlSession
SqlSessionFactoryBuilder : build(InputStream in) 获取SqlSessionFactory
SqlSessionFactory : openSession() 获取SqlSession
SqlSession:
selectOne(String id , Object param);
selectList(String id ,Object param);
insert()
delete()
update()
CRUD
增加
添加用户信息
这里提供了操作的实体类User.java
:
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// getter and setter, toString() 省略
}
![](https://img.haomeiwen.com/i2987011/d046038d5866f14f.png)
<!-- 添加用户信息 增删改没有输出参数
parameterType: 因为要插入的参数是一个User对象, 所以类型就是User
#{}: 占位符中填写User的属性
这个取值跟EL表达式取值不一样, 这里就是成员变量的变量名或getXxx()的后面一部分中的其中一个
如何将主键的值会写到User对象中(主键自增情况下的回写)
<selectKey>: 用来将主键回写
keyProperty: 查询出来的主键对应着user对象的哪个属性
order: 查询主键语句在插入语句的前面还是后面执行
resultType: 查询出来的主键的类型
UUID生成的主键回写
-->
<insert id="insertUser" parameterType="com.wtu.entity.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
select last_insert_id()
</selectKey>
<!-- 在插入语句之前, 先执行UUID() 函数, 生成一个主键, 然后主键设置到user对象中,
然后将user对象插入数据库, 所以order="BEFORE" -->
<!--<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">-->
<!--select uuid()-->
<!--</selectKey>-->
insert into `user` values(#{id}, #{username}, #{birthday}, #{sex}, #{address})
</insert>
单元测试代码:
@Test
public void insertUser() throws Exception {
// 得到全局配置文件的输入流对象
InputStream in = Resources.getResourceAsStream("mybatis/sqlMapConfig.xml");
// 得到Session工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 得到SqlSession
SqlSession session = factory.openSession();
// 创建User对象
User user = new User();
user.setUsername("小阳");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("湖北武汉");
// 调用的是session的insert()方法
/*
还传入的参数是一个User对象
如果是增删改, 一定要记得提交事务
*/
session.insert("demo.insertUser", user);
System.out.println(user);
session.commit();
// 关闭session
session.close();
}
查询
分多种查询
<select id="findAllUser" resultType="com.wtu.entity.User">
select * from `user`
</select>
<!--
id: 代表这条sql语句, 在SqlSession调用方法的时候, 需要该id作为参数进行传递, 在整个文件中不能重复
parameterType: 参数的类型
resultType:User
#{}:这是个占位符, 如果参数是简单类型(基本类型+String), 那么里面可以随便写.
-->
<select id="findUserById" parameterType="int" resultType="com.wtu.entity.User">
select * from `user` where id = #{id}
</select>
<!-- 根据姓名模糊查询
resultType: 查询结果中, 单条结果集映射的数据类型
parameterType: 输入参数类型
${} : 不是占位符, 它是拼接字符串的一个连接符, 如果传入的参数是简单类型,
那么接收的变量只能是value, 它会将接收的参数原封不动, 不加任何修饰地拼接到SQL语句中 -->
<select id="findUserByName" parameterType="java.lang.String"
resultType="com.wtu.entity.User">
select * from `user` where username like '%${value}%'
</select>
这里的单元测试略, 和上面类似.
改
映射文件:
<!-- 修改用户信息 -->
<update id="updateUser" parameterType="com.wtu.entity.User">
update `user` set username = #{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id=#{id}
</update>
删除
相关映射文件:
<!-- 根据id删除用户 -->
<delete id="deleteById" parameterType="java.lang.Integer">
delete from user where id = #{abc}
</delete>
注意事项:
// 如果查询结果集中有多条记录, 那么使用selectList(String id, Object param)
/*
如果这时使用selectOne, 那么这时会抛出
org.apache.ibatis.exceptions.TooManyResultsException:
Expected one result (or null) to be returned by selectOne(), but found: 3
异常
*/
映射文件SQL语句书写小结
parameterType
: 输入参数类型, 一定是类型的全名, 或者是别名(int)
resultType
: 输出参数类型
#{}
: 占位符, 如果是简单类型, 那么里面可以随便写
${}
: 是连接符, 如果是简单类型, 那么里面只能是value
如果parameterType
是自定义类的类型, 那么#{}, ${}里面都要写该类型的属性名
Mybatis开发Dao层
SqlSession对象: 该对象是线程不安全的, 所以不能定义为成员变量, 只能定义局部变量.
SqlSessionFactory: 该对象在项目中只需要一个, 那么该变量可以定义为成员变量
Dao 接口
![](https://img.haomeiwen.com/i2987011/6b018864e6ee6ed7.png)
Dao的实现类:
public class UserDaoImpl implements UserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public void addUser(User user) throws Exception {
SqlSession session = factory.openSession();
session.insert("demo.insertUser", user);
session.commit();
session.close();
}
@Override
public void deleteUser(Integer id) throws Exception {
SqlSession session = factory.openSession();
session.delete("demo.deleteById", id);
session.commit();
session.close();
}
@Override
public void updateUser(User user) throws Exception {
SqlSession session = factory.openSession();
session.update("demo.updateUser", user);
session.commit();
session.close();
}
@Override
public User findUserById(Integer id) throws Exception {
// 获取SqlSession对象
SqlSession session = factory.openSession();
User user = session.selectOne("demo.findUserById", id);
return user;
}
@Override
public List<User> findAllUser() throws Exception {
SqlSession session = factory.openSession();
List<User> userList = session.selectList("demo.findAllUser");
return userList;
}
}
测试类:
public class UserDaoImplTest {
private SqlSessionFactory factory;
// 用来获取工厂
@Before
public void getFactory() throws Exception {
this.factory = new SqlSessionFactoryBuilder().
build(Resources.getResourceAsStream("mybatis/sqlMapConfig.xml"));
}
@Test
public void testFindUser() throws Exception {
// 获取UserDao对象
UserDao userDao = new UserDaoImpl(factory);
User user = userDao.findUserById(28);
System.out.println(user);
}
@Test
public void testFindAllUser() throws Exception {
// 获取UserDao对象
UserDao userDao = new UserDaoImpl(factory);
List<User> userList = userDao.findAllUser();
System.out.println(userList);
}
@Test
public void testAddUser() throws Exception {
// 获取UserDao对象
UserDao userDao = new UserDaoImpl(factory);
User user = new User();
user.setUsername("小花");
user.setSex("2");
user.setBirthday(new Date());
user.setAddress("武汉");
userDao.addUser(user);
System.out.println(user);
}
@Test
public void deleteUser() throws Exception {
// 获取UserDao对象
UserDao userDao = new UserDaoImpl(factory);
userDao.deleteUser(29);
}
@Test
public void updateUser() throws Exception {
// 获取UserDao对象
UserDao userDao = new UserDaoImpl(factory);
User user = new User();
user.setId(28);
user.setUsername("小阳");
user.setSex("2");
user.setBirthday(new Date());
user.setAddress("武汉");
userDao.updateUser(user);
System.out.println(user);
}
}
Mapper代理模式开发DAO
上面的dao传统开发模式有两个不太好的地方:
- 我们需要在每个dao方法中去获取SqlSession对象, 并且调用SqlSession中的方法, 在每个方法中都是差不多的.
- 在每次调用SqlSession方法的时候, 传入的第一个参数都是硬编码, 写死了, 不好!!!
由此, 我们引入Mapper代理模式开发dao层
因为在每个dao的实现类的方法中, 存在大量的模板代码, 那么这些代码, 可以通过一个代理对象来调用. 但是这个代理对象根据dao的接口来生成, 所以在Mapper代理开发中, 我只需要编写dao的接口, 不需要编写其实现类.
1. 准备映射文件
如果采用的mapper代理开发模式, 那么映射文件的命名空间必须是
Mapper接口的全路径
![](https://img.haomeiwen.com/i2987011/cf02d8b11c06c034.png)
2. 编写接口中的方法
三个特点:
1. Mapper接口中的方法名必须和映射文件中的sql语句的id一致
2. Mapper接口中的方法的返回值类型必须和resultType一致, 如果返回的
结果有多条记录, 那么返回值是List, 但是元素类型和resultType一致
3. Mapper接口中的方法参数必须和映射文件中parameterType一致
见代码:
/**
* mapper开发的接口
* Created by menglanyingfei
* on 2018/1/20.
*/
public interface UserMapper {
List<User> findUserByName(String name);
}
<?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代理开发模式, 那么映射文件的命名空间必须是
Mapper接口的全路径 -->
<mapper namespace="com.wtu.mapper.UserMapper">
<!-- 根据姓名进行模糊查询 -->
<select id="findUserByName" parameterType="java.lang.String"
resultType="com.wtu.entity.User">
select * from `user` where username like '%${value}%'
</select>
</mapper>
测试代码:
public class MapperTest {
private SqlSessionFactory factory;
@Before
public void getFactory() throws Exception {
this.factory = new SqlSessionFactoryBuilder().
build(Resources.getResourceAsStream("mybatis/sqlMapConfig.xml"));
}
@Test
public void testFindUserByName() {
// 获取SqlSession
SqlSession session = factory.openSession();
// 通过SqlSession对象得到Mapper接口的一个代理对象
// 需要传递的参数是Mapper接口的类型
UserMapper userMapper = session.getMapper(UserMapper.class);
// 通过代理对象调用UserMapper中的方法
List<User> userList = userMapper.findUserByName("小");
System.out.println(userList);
// 红色标识!
System.err.println(userMapper);
// org.apache.ibatis.binding.MapperProxy@289d1c02
// 关闭资源
session.close();
}
Mybatis全局配置文件的一些配置信息
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
-- environment(环境子属性对象)
---- transactionManager(事务管理)
---- dataSource(数据源)
mappers(映射器)
注意:上面几项配置, 必须按照顺序进行配置
settings(全局配置参数)
该配置中可以配置全局参数, 比如二级缓存, 延迟加载和日志信息的配置
配置信息参考: mybatis-settings.xlsx(包含在下面的代码文件夹中)
<!-- 在控制台输出日志信息 -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
properties: 一般用来配置数据库的连接信息
在properties标签体, 还可以通过property标签来设置属性, 但是此时的加载顺序是先加载的是property标签中的属性, 然后再加载resource指定的外部属性文件中的属性.
<!-- 加载数据库配置信息的属性文件, 跟spring整合以后就没了 -->
<properties resource="dbConfig.properties">
<!--<property name="user" value="root1"/>-->
</properties>
typeAliases(类型别名, 重要)
Mybatis默认支持的别名
![](https://img.haomeiwen.com/i2987011/46ea95dbfb5ab9f8.png)
自定义别名:
-
配置单个别名:
image.png
-
批量配置别名: mybatis扫描某一个包, 对包下的所有类都创建别名
image.png
<!-- 配置自定义类型别名
type: 本来的类型
alias: 别名
<package name=""/>: 批量扫描某一个包, 如果是批量配置别名, 那么
别名就是类名, 并且首字母大写和小写都可以
-->
<typeAliases>
<!-- 配置单个别名 -->
<!--<typeAlias type="com.wtu.entity.User" alias="user"/>-->
<package name="com.wtu.entity"/>
</typeAliases>
typeHandlers(类型处理器)
一般不需要配置
![](https://img.haomeiwen.com/i2987011/a39e299a8b39b292.png)
补充(但使用较少!):
<!-- environments 里面的内容在mybatis和spring整合以后, 就全部没了 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
mappers(映射器)
用来在全局配置文件中加载映射文件
加载单个映射文件
<mappers>
<!-- 将映射文件加载到全局配置文件 -->
<mapper resource="mybatis/user.xml"/>
<!-- 将UserMapper.xml加入到全局配置文件 -->
<!--<mapper resource="mapper/UserMapper.xml"/>-->
<!-- 通过mapper接口来加载映射文件
1. 映射文件的文件名必须和接口名相同
2. 映射文件必须和Mapper接口处于同一个目录下
3. 这种方式只适合Mapper代理开发模式 -->
<!--<mapper class="com.wtu.mapper.UserMapper"/>-->
<!-- 批量加载映射文件, 自动去扫描某一个包, 将该包下的所有映射文件加载, 推荐使用! -->
<package name="com.wtu.mapper"/>
</mappers>
完!
完整代码地址见:
https://github.com/menglanyingfei/SSMLearning/tree/master/mybatis_day02