从零开始认识并操纵Mybatis
Posted Autom_liu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始认识并操纵Mybatis相关的知识,希望对你有一定的参考价值。
目录
- 业务介绍
- 版本声明
- 认识Mybatis
- 入门程序操作步骤
- 原始Dao开发方式存在的问题
- Mapper 动态代理方式
- 优化配置文件
- 关于Mappers映射器属性
- 输入类型和输出类型
- 动态sql完成多条件查询
- resultMap 输出映射,完成订单信息
- 用户订单关联查询
- 结束
@(认识并操纵Mybatis)
声明:从零开始,并不代表你对java Mybatis一点都不懂的程度哈,本实例只是过一个概貌,详细内容会分多篇文章拆解
业务介绍
用户模块的管理,用户表的维护:
- 添加用户
- 修改用户信息
- 删除用户
- 查询用户
- id查询单个
- 用户名模糊查询
- 查询所有
- 查询总数
- 根据ids查询
- 多条件查询
用户和订单关联管理
- 根据订单查询对应用户信息
- 根据用户查询所有订单信息
版本声明
只使用Mybatis
,不集成其他框架。Mybatis版本为^3.4
开发工具使用eclipse
使用的是纯 java Project
整个流程的详细项目代码可以踩我的github结合观光:https://github.com/Autom-liu/MybatisLearn 每一个步骤都分得很清晰哦,可以clone下来再研究
认识Mybatis
是什么
MyBatis是一个优秀的持久层框架,不算完整的ORM框架
历史
- MyBatis 本是apache的一个开源项目iBatis,
- 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
- 2013年11月迁移到Github。
传统JDBC存在的问题
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
- Mybatis 在配置文件中配置了数据连接池,使用连接池管理数据库链接。
- Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
- Mybatis 将Sql语句配置在配置文件中与java代码分离。
- 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
- Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
- 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
- Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
与完整的ORM框架Hibernate的区别
- Mybatis不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
- Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
- Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
- 总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
简单来说,Mybatis为什么流行,Hibernate为什么落幕,终归在于移动互联网的世界趋势,迭代更新快,要求变化杂而多。不可能再像以往那样,形成完整稳定的需求再开发的阶段了....越贴近底层原生越有价值,越是封装包装限制也越大
Mybatis架构
入门程序操作步骤
下载和导包
下载链接:https://github.com/mybatis/mybatis-3/releases
目录结构:
导包:
- Mybatis核心包
- Mybatis依赖包
- 数据库驱动包 注:这里使用的是老版本的5.1的包,新版本有很大改动!
准备数据库和Java Bean
两张表: 用户表 + 订单表 基本创建即可,无其他关联
准备javaBean 就不多说了,要注意的一点就是,javaBean最好实现
Serializable
序列化接口持久化,还有一点就是命名规范吧,不同人说法不同,有些是直接干脆和数据库字段名一样,而这里不那么一致,使用编程语言中统一的驼峰命名法,而不是数据库中的短划线分割。
要相信的是,任何一个框架总会帮我们解决这个映射问题的!
配置Mybatis
在官方给出的文档当中给出了我们这样的配置:
官方的文件名是: mybatis-config.xml 当然这并不是定死的。
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
这个是主配置文件,也就是架构当中的最顶层,基本上参照这个配置copy,稍作修改即可,就不再导入约束啥的了。
比如在本次配置里,创建的是src/sqlMap.xml,文件名不是固定的,后边程序会指定
还有一点就是:不用太在意每个配置项的含义,大概看得懂就行,因为后期往往需要整合spring,这些配置全部不用了,作为入门程序而已,给大家观光一下
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="****"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sqlMap/User.xml"/>
</mappers>
</configuration>
编写sql映射文件
这里可以看到Mapper标签,是引入其他映射文件,这个文件就是架构当中的第二层,多个mapper的组成,每个mapper都是若干个sql语句
到这里就需要考虑实现业务的sql语句应该如何书写了,这里先只贴出增和查的业务,剩下都以举例完成
sqlMap/User.xml
<mapper namespace="user">
<select id="findUser" resultType="edu.scnu.bean.User" parameterType="Integer">
select * from mb_user where id = #{id}
</select>
<select id="getByUsername" resultType="edu.scnu.bean.User" parameterType="String">
select * from mb_user where username like "%"#{username}"%"
</select>
<insert id="save" parameterType="edu.scnu.bean.User">
insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
</insert>
</mapper>
namespace 是命名空间,这个真正的妙用后面体现出来,这里暂时只需要知道用于区分同名作用域即可
id 可以认为是该条sql语句的标识符
parameterType 传递进来的参数类型,可以看到基本数据类型可以省略,而其他的就要完整类名了
resultType 返回值类型,同上一样
select 标签用于书写select查询语句
insert 标签用于书写insert插入语句 update delete一样
尝试了一下 select 书写insert好像也可以,但不推荐
关于模糊查询的补充
在Mybatis中使用 #{}
作为占位符,占位符的作用在于会在标识符取得结果后前后加单引号。
如果直接使用:
select * from mb_user where username like "%#{username}%"
就不对了,因为假如username=admin 结果变为:
select * from mb_user where username like "%‘admin‘%"
就语法错误或没有结果
可以使用 ${}
这种字符串拼接的方式
select * from mb_user where username like "%${username}%"
不过这样只是没有了占位符参数的效果了而已。
书写Dao类
接下来就到来书写真正的Dao类了,这里还是按照规范实现接口
public interface UserDao {
void save(User user);
void update(User user);
void delete(Integer id);
User find(Integer id);
// List<User> getAll();
// Integer getCount();
List<User> getByUsername(String username);
// List<User> getByCond(User user);
}
看下实现类:
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
// 没有spring帮助下,就需要外部手动注入!
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public void save(User user) {
SqlSession session = sqlSessionFactory.openSession();
session.insert("user.save", user);
session.commit();
}
@Override
public User find(Integer id) {
SqlSession session = sqlSessionFactory.openSession();
return session.selectOne("user.findUser", id);
}
@Override
public List<User> getByUsername(String username) {
SqlSession session = sqlSessionFactory.openSession();
return session.selectList("user.getByUsername", username);
}
}
测试类
最后即可编写测试了
public class MybatisTest {
private SqlSessionFactory sqlSessionFactory;
private UserDao userDao;
@Before
public void before() throws Exception{
String resource = "sqlMap.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
userDao = new UserDaoImpl(sqlSessionFactory);
}
@Test
public void testFind() {
userDao.find(1);
}
@Test
public void testGetByUsername() {
userDao.getByUsername("王");
}
@Test
public void testSave() {
userDao.save(new User("未知用户", "2", new Date(), "中国"));
}
}
返回id 的save
现在对实现增加用户新增需求,就是在新增用户的同时根据该用户ID查询该订单信息
这时候我们首先要获取新增用户后的id,但是新增用户是数据库给我们返回的是受影响条数,如何快速获得最新插入数据的id呢
sql语法提供了 select LAST_INSERT_ID()
供我们获取最新插入数据的ID,我们可以将该sql嵌入到sql配置文件中去
这里简单讲解配置语法:
- selectKey 标签实现主键返回
- keyColumn:主键对应的表中的哪一列
- keyProperty:主键对应的pojo(Bean)中的哪一个属性
- order:设置在执行insert语句前执行查询id的sql,还是在执行insert语句之后执行查询id的sql
使用主键自增策略的id:
<!-- 生成主键的顺序Mysql是之后,oracle是之前 -->
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
使用uuid的String
<selectKey keyColumn="id" keyProperty="id" order="BEFORE" resultType="string">
select LAST_INSERT_ID()
</selectKey>
这里可以修改上述插入SQL语法配置:
<insert id="save" parameterType="edu.scnu.bean.User">
<!-- 生成主键的顺序Mysql是之后,oracle是之前 -->
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
</insert>
原始Dao开发方式存在的问题
- Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
- 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。
上述问题主要关键还是在于同类型的枯燥性重复性代码,只有少数参数传递部分要变动,其它基本都是同样的套路,因此可不可以有个办法一些重复性工作呢?Mybatis贴心为我们服务了这一点,它省去的不仅仅是一两条语句,它把整个实现类都为我们省去了。来看看它是怎么工作的吧!
Mapper 动态代理方式
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
也就是说,我们要完成的只是接口,实现类框架帮我们搞定了...
那么首先来修改优化一下配置文件
<!-- 使用动态代理开发DAO,1. namespace必须和Mapper接口类路径一致 -->
<mapper namespace="edu.scnu.dao.UserDao">
<!-- 根据用户id查询用户 -->
<!-- 2. id必须和Mapper接口方法名一致 -->
<!-- 3. parameterType必须和接口方法参数类型一致 -->
<!-- 4. resultType必须和接口方法返回值类型一致 -->
<select id="find" resultType="edu.scnu.bean.User" parameterType="Integer">
select * from mb_user where id = #{id}
</select>
<select id="getByUsername" resultType="edu.scnu.bean.User" parameterType="String">
select * from mb_user where username like "%"#{username}"%"
</select>
<insert id="save" parameterType="edu.scnu.bean.User">
<!-- 生成主键的顺序Mysql是之后,oracle是之前 -->
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
</insert>
</mapper>
接下来如果把实现类删除了,我们发现测试类实例化就报错了,这时候我们需要通过session的getMapper方法获得Mybatis帮我们创建好的实现类
测试类:
@Before
public void before() throws Exception{
String resource = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
userDao = session.getMapper(UserDao.class);
}
优化配置文件
到目前为止,我们书写了两个配置文件,这两个配置文件开始出现不仅繁杂不好维护的问题,还有挺多不规范的地方:
- 数据库连接参数不应该直接在xml配置当中,应该提取出来:
使用property属性
<!-- 是用resource属性加载外部配置文件 -->
<properties resource="db.properties">
<!-- 在properties内部用property定义属性 -->
<!-- 如果外部配置文件有该属性,则内部定义属性被外部属性覆盖 -->
<property name="jdbc.password" value="root" />
</properties>
<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>
- 类名过长重复
使用typeAliases属性自定义别名
mybatis已经为我们提供了很多基本数据类型以及包装类的别名,同样,我们也可以自己自定义类名别名
支持单个别名的定义
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="edu.scnu.bean.User" />
</typeAliases>
还有批量定义,这里选用批量定义的方式
<typeAliases>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(大小写不敏感) -->
<package name="edu.scnu.bean" />
</typeAliases>
这样后续的所有ResultType 和 parameterType只需要直接写User或user即可
诡异的是,也是需要注意的是顺序问题
typeAliases 必须紧跟 property 后
关于Mappers映射器属性
resource
- 使用相对于类路径的资源(现在的使用方式)
- 如:
class
- 使用mapper接口类路径
- 如:
- 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
- 注册指定包下的所有mapper接口
- 如:
- 注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
- 用于批量引入
输入类型和输出类型
接下来我们来完善其他业务功能,这里为了演示以下各类几种输入和输出类型的情况,我们增加一个实体类型包装实体
输入类型:
- 基本类型
- 实体类型
- 实体类型包装类
说白了传递引用类型,会自动把所有引用属性转化为map对象,这点和struts的OGNL表达式类似
输出类型:
- 基本类型
- 实体
- 实体列表
- 只需要传递泛型类型(集合元素的类型即可)
然后来完善我们其他功能
parameterType为实体包装类的情况
<update id="update" parameterType="UserWrapper">
update mb_user set username=#{user.username}, birthday=#{user.birthday}, sex=#{user.sex}, address=#{user.address} where id=#{user.id}
</update>
resultType为基本类型的情况
<select id="getCount" resultType="int">
select count(*) from mb_user
</select>
动态sql完成多条件查询
使用where标签和if标签
<select id="getByCond" parameterType="User" resultType="User">
select * from mb_user
<where>
<if test="sex != null and sex != ‘‘">
and sex = #{sex}
</if>
<if test="username != null and username != ‘‘">
and username = #{username}
</if>
<if test="address != null and address != ‘‘">
and address = #{address}
</if>
</where>
</select>
使用sql片段简化
sql标签声明 include引入
<sql id="selectSql">
select * from ub_user
</sql>
<select id="find" resultType="User" parameterType="Integer">
<include refid="selectSql" /> where id = #{id}
</select>
使用foreach标签实现多id查询
原理
select * from mb_user where id in (1,2,3);
以java数组传递参数
<select id="getByIds" parameterType="Integer" resultType="User">
<include refid="selectSql" />
<where>
<foreach collection="array" item ="id" separator="," open="id in (" close=")">
#{id}
</foreach>
</where>
</select>
以list集合传递参数
<select id="getByIds" parameterType="Integer" resultType="User">
<include refid="selectSql" />
<where>
<foreach collection="list" item ="id" separator="," open="id in (" close=")">
#{id}
</foreach>
</where>
</select>
resultMap 输出映射,完成订单信息
之前我们说到,订单Dao类是以驼峰命名的,而数据库字段是下划线命名的
在之前同名的情况都可以自动映射,那么针对这种不同名的情况又如何呢?
我们先简单完成以下订单的其中一个业务
public interface OrdersDao {
List<Orders> getAll();
}
用户订单关联查询
一对一
方式一、使用新建实体类继承关系
新建一个OrdersUser 的javaBean类
public class OrdersUser extends Orders {
private String username;
private String address;
// ...
}
配置OrdersUser.xml
<mapper namespace="edu.scnu.dao.OrdersUserDao">
<select id="queryAll" resultType="OrdersUser">
SELECT
o.id,
o.user_id
userId,
o.number,
o.createtime,
o.note,
u.username,
u.address
FROM
`order` o
LEFT JOIN `user` u ON o.user_id = u.id
</select>
</mapper>
方式二、直接多对一关系映射
直接在Orders实体类下增加User字段,并在配置文件里作如下映射:
<resultMap type="Orders" id="orderResultMap">
<!-- 定义主键 ,非常重要。如果是多个字段,则定义多个id -->
<id property="id" column="id"></id>
<result property="userId" column="user_id"/>
<!-- 关联,属性全写上 -->
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
<association property="user" javaType="User">
<!-- id:声明主键,表示user_id是关联查询对象的唯一标识-->
<id property="id" column="user_id" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
</resultMap>
一对多关系
一对多关系只能使用resultMap的collection映射
同样在User Bean类中添加List<Orders>字段
private List<Orders> orders;
<resultMap type="User" id="UserOrdersResultMap">
<id property="id" column="id" />
<result property="username" column="username"/>
<result property="address" column="address"/>
<collection property="orders" javaType="List" ofType="Orders">
<result property="id" column="oid"/>
<result property="createtime" column="createtime"/>
<result property="number" column="number"/>
</collection>
</resultMap>
<select id="getWithOrders" resultMap="UserOrdersResultMap">
SELECT
u.id,
u.username,
u.address,
o.id oid,
o.number,
o.createtime
FROM
`mb_user` u
LEFT JOIN `mb_orders` o ON u.id = o.user_id
</select>
结束
好了,到这里已经介绍了单独使用Mybatis操纵的所有功能,整个流程到这里就算完成了,整个流程的详细项目代码可以踩我的github结合观光:https://github.com/Autom-liu/MybatisLearn ,整个流程涉及到诸多知识细节需要慢慢琢磨,这里只给大家展示整个概貌,具体细节会在后续的文章中慢慢解释,还会给大家展示Mybatis和Spring整合后的精彩内容哦,期待大家的支持和star~~~
以上是关于从零开始认识并操纵Mybatis的主要内容,如果未能解决你的问题,请参考以下文章