MyBatis入门第2天--MyBatis基础知识
Posted 鹿天斐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis入门第2天--MyBatis基础知识相关的知识,希望对你有一定的参考价值。
文档版本 | 开发工具 | 测试平台 | 工程名字 | 日期 | 作者 | 备注 |
---|---|---|---|---|---|---|
V1.0 | 2016.06.26 | lutianfei | none |
mybatis开发dao的方法
SqlSession使用范围
SqlSessionFactoryBuilder
- 通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory
- 将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。
- 在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。
SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。
- 将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。
SqlSession
- SqlSession是一个面向用户(程序员)的接口。
- SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、。
- SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
- SqlSession最佳应用场合在方法体内,定义成局部变量使用。
SqlSession执行过程如下:
- 1、 加载数据源等配置信息
- Environment environment = configuration.getEnvironment();
- 2、 创建数据库链接
- 3、 创建事务对象
- 4、 创建Executor,SqlSession所有操作都是通过Executor完成,mybatis源码如下:
- 1、 加载数据源等配置信息
if (ExecutorType.BATCH == executorType) {
executor = newBatchExecutor(this, transaction);
} elseif (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}
* 5、 SqlSession的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
- 结论:
- 每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
- 打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
原始dao开发方法
- 程序员需要写dao接口和dao实现类
思路
- 程序员需要写dao接口和dao实现类。
- 需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession
dao接口
- dao接口实现类
public class UserDaoImpl implements UserDao {
// 需要向dao实现类中注入SqlSessionFactory
// 这里通过构造方法注入
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById", id);
// 释放资源
sqlSession.close();
return user;
}
@Override
public List<User> findUserByName(String name) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("test.findUserByName", name);
// 释放资源
sqlSession.close();
return list;
}
@Override
public void insertUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行插入操作
sqlSession.insert("test.insertUser", user);
// 提交事务
sqlSession.commit();
// 释放资源
sqlSession.close();
}
@Override
public void deleteUser(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行插入操作
sqlSession.delete("test.deleteUser", id);
// 提交事务
sqlSession.commit();
// 释放资源
sqlSession.close();
}
- 测试代码:
public class UserDaoImplTest {
private SqlSessionFactory sqlSessionFactory;
// 此方法是在执行testFindUserById之前执行
@Before
public void setUp() throws Exception {
// 创建sqlSessionFactory
// mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建会话工厂,传入mybatis的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
// 创建UserDao的对象
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
// 调用UserDao的方法
User user = userDao.findUserById(1);
System.out.println(user);
}
}
原始dao开发问题
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
- 调用sqlsession方法时将statement的id硬编码了
- 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
mapper代理方法
- 程序员只需要mapper接口(相当 于dao接口)
mapper代理开发规范
- 程序员还需要编写mapper.xml映射文件
程序员编写mapper接口需要遵循一些开发规范,这样Mybatis可以自动生成mapper接口实现类代理对象。
开发规范:
- 1、在mapper.xml中namespace等
于mapper接口地址
- 2、mapper.java接口中的方法名和mapper.xml中statement的id一致
- 3、mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。
- 4、mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
- 1、在mapper.xml中namespace等
- 总结:
- 以上开发规范主要是对下边的代码进行统一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);
- 编写XXXmapper.java
public interface UserMapper {
//用户信息综合查询
public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;
//用户信息综合查询总数
public int findUserCount(UserQueryVo userQueryVo) throws Exception;
//根据id查询用户信息
public User findUserById(int id) throws Exception;
//根据id查询用户信息,使用resultMap输出
public User findUserByIdResultMap(int id) throws Exception;
//根据用户名列查询用户列表
public List<User> findUserByName(String name)throws Exception;
//插入用户
public void insertUser(User user)throws Exception;
//删除用户
public void deleteUser(int id)throws Exception;
}
- XXXmapper.xml
- 在SqlMapConfig.xml中加载mapper.xml
<!-- 加载 映射文件 -->
<mappers>
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
<!-- 批量加载mapper
指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载,遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中
上边规范的前提是:使用的是mapper代理方法
-->
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
- 测试
@Test
public void testFindUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
User user = userMapper.findUserById(1);
System.out.println(user);
}
- 一些问题总结
- 代理对象内部调用selectOne或selectList ?
- 如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库。
- 如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。
- mapper接口方法参数只能有一个是否影响系统扩展维护?
- 系统框架中,dao层的代码是被业务层公用的。即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
- 注意:持久层方法的参数可以包装类型、map、list等等;
- 但service方法中建议不要使用包装类型(不利于业务层的可扩展)。
- 代理对象内部调用selectOne或selectList ?
SqlMapConfig.xml配置文件
- mybatis的全局配置文件SqlMapConfig.xml,配置内容如下:
- properties(属性)
- settings(全局配置参数)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理)
- dataSource(数据源)
- mappers(映射器)
properties属性
需求:将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。
在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。
- 将数据库连接参数只配置在db.properties中:方便对参数进行统一管理,其它xml可以引用该db.properties。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=mysql
- 在sqlMapConfig.xml加载属性文件
<!-- 加载属性文件 -->
<properties resource="db.properties">
<!--properties中还可以配置一些属性名和属性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 数据库连接池,由mybatis管理-->
<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>
</environment>
</environments>
- properties特性:
- 注意: MyBatis 将按照下面的顺序来加载属性:
- 在 properties 元素体内定义的属性首先被读取。
- 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
- 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
因此,通过
parameterType
传递的属性具有最高优先级,resource
或url
加载的属性次之,最低优先级的是properties
元素体内定义的属性。建议:
- 不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
- 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
settings全局参数配置
- mybatis框架在运行时可以调整一些运行参数。
- 比如:开启二级缓存、开启延迟加载。。
- 全局参数将会影响mybatis的运行行为。
- 详细参见“学习资料/mybatis-settings.xlsx”文件
typeAliases(别名)重点
需求
- 在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。
- 如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType 指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
mybatis默认支持的别名
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
- 自定义别名
单个别名定义
引用别名:
批量定义别名(常用)
<!-- 别名定义 -->
<typeAliases>
<!-- 针对单个别名定义
type:类型的路径
alias:别名
-->
<!-- <typeAlias type="cn.itcast.mybatis.po.User" alias="user"/> -->
<!-- 批量别名定义
指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以)
-->
<package name="cn.itcast.mybatis.po"/>
</typeAliases>
typeHandlers(类型处理器)
- mybatis中通过typeHandlers完成jdbc类型和java类型的转换。
- 通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义
- mybatis支持类型处理器:
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | Boolean,boolean | 任何兼容的布尔值 |
ByteTypeHandler | Byte,byte | 任何兼容的数字或字节类型 |
ShortTypeHandler | Short,short | 任何兼容的数字或短整型 |
IntegerTypeHandler | Integer,int | 任何兼容的数字和整型 |
LongTypeHandler | Long,long | 任何兼容的数字或长整型 |
FloatTypeHandler | Float,float | 任何兼容的数字或单精度浮点型 |
DoubleTypeHandler | Double,double | 任何兼容的数字或双精度浮点型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的数字或十进制小数类型 |
StringTypeHandler | String | CHAR和VARCHAR类型 |
ClobTypeHandler | String | CLOB和LONGVARCHAR类型 |
NStringTypeHandler | String | NVARCHAR和NCHAR类型 |
NClobTypeHandler | String | NCLOB类型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB和LONGVARBINARY类型 |
DateTypeHandler | Date(java.util) | TIMESTAMP类型 |
DateOnlyTypeHandler | Date(java.util) | DATE类型 |
TimeOnlyTypeHandler | Date(java.util) | TIME类型 |
SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP类型 |
SqlDateTypeHandler | Date(java.sql) | DATE类型 |
SqlTimeTypeHandler | Time(java.sql) | TIME类型 |
ObjectTypeHandler | 任意 | 其他或未指定类型 |
EnumTypeHandler | Enumeration类型 | VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 |
mappers(映射配置)
- 通过resource加载单个映射文件,
<mapper resource=" " />
使用相对于类路径的资源
- 如:
<mapper resource="sqlmap/User.xml" />
- 如:
使用完全限定路径
<mapper url=" " />
- 如:
<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
- 如:
通过mapper接口加载单个mapper
- 规范:mapper**接口类名和mapper.xml映射文件名称必须保持一致,且在一个package目录中**;并且要使用mapper代理方法
<!-- 通过mapper接口加载单个映射文件需要遵循一些规范:mapper接口类名和mapper.xml映射文件名称必须保持一致,且在一个package目录中;并且要使用mapper代理方法-->
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
- 批量加载mapper(推荐使用)
- 当指定mapper接口的包名时,mybatis会自动扫描包下边所有mapper接口进行加载
- 应遵循的一些规范:需要将mapper**接口类名和mapper.xml映射文件名称保持一致,且在一个package目录中;并且使用的是**mapper代理方法
<package name="cn.itcast.mybatis.mapper"/>
输入映射
- 通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型。
传递pojo的包装对象
需求:
- 完成用户信息的综合查询,需要传入查询条件很复杂(可能包括用户信息、其它信息,比如商品、订单的)
定义包装类型pojo
- 针对上边需求,建议使用自定义的包装类型的pojo。在包装类型的pojo中将复杂的查询条件包装进去。
- 映射文件XXXmapper.xml设置
- 在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。
mapper.java
测试代码
传递hashmap
- Sql映射文件定义如下:
#
中 id 和 username是hashmap的key。
<!-- 传递<u>hashmap</u>综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
select * from user where id=#{id} and username like ‘%${username}%‘
</select>
- 测试:
Public void testFindUserByHashmap()throws Exception{
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//构造查询条件Hashmap对象
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("username", "管理员");
//传递Hashmap对象查询用户列表
List<User>list = userMapper.findUserByHashmap(map);
//关闭session
session.close();
}
- 异常测试:
- 传递的map中的key和sql中解析的key不一致。
- 测试结果没有报错,只是通过key获取值为空。
输出映射
resultType
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
- 如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
- 只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象。
输出简单类型
需求
- 用户信息的综合查询列表总数,通过查询总数和上边用户综合查询列表才可以实现分页。
Usermapper.xml
Usermapper.java
测试代码
小结
- 查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
输出pojo对象和pojo列表
- 不管是输出的pojo单个对象还是一个列表(list中包括pojo),在mapper.xml中resultType指定的类型是一样的。
在mapper.java指定的方法返回值类型不一样:
- 1、输出单个pojo对象,方法返回值是单个对象类型
- 2、输出pojo对象list,方法返回值是
List<Pojo>
生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList (返回集合对象调用)
resultMap
mybatis中使用resultMap完成高级输出结果映射。
resultMap使用方法
- 如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
案例: 将下边的sql使用User完成映射
- User类中属性名和上边查询列名不一致。
SELECT id id_,username username_ FROM USER WHERE id=#{value}
- 1、在UserMapper.xml中定义resultMap
<!-- 定义resultMap
将SELECT id id_,username username_ FROM USER 和User类中的属性作一个映射关系
type:resultMap最终映射的java对象类型,可以使用别名
id:对resultMap的唯一标识
-->
<resultMap type="user" id="userResultMap">
<!-- id表示查询结果集中唯一标识
column:查询出来的列名
property:type指定的pojo类型中的属性名
最终resultMap对column和property作一个映射关系 (对应关系)
-->
<id column="id_" property="id"/>
<!--
result:对普通名映射定义
column:查询出来的列名
property:type指定的pojo类型中的属性名
最终resultMap对column和property作一个映射关系 (对应关系)
-->
<result column="username_" property="username"/>
</resultMap>
- 2、使用resultMap作为statement的输出映射类型
<!-- 使用resultMap进行输出映射
resultMap:指定定义的resultMap的id,如果这个resultMap在其它的mapper文件,前边需要加namespace
-->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
SELECT id id_,username username_ FROM USER WHERE id=#{value}
</select>
mapper.java
测试
@Test
public void testFindUserByIdResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
User user = userMapper.findUserByIdResultMap(1);
System.out.println(user);
}
- 小结
- 使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
- 如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
动态sql
什么是动态sql
- mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
需求
- 用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql。
- 对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。
mapper.xml
- 测试代码
sql片段
需求
- 将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。方便程序员进行开发。
定义sql片段,在UserMapper.xml顶部
- 引用sql片段
- 在mapper.xml中定义的statement中引用sql片段:
foreach
向sql传递数组或List,mybatis使用foreach解析
需求
- 在用户查询列表和查询总数的statement中增加多个id输入查询。sql语句如下:
//两种方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
- 在输入参数类型中添加
List<Integer>
ids传入多个id
- 修改mapper.xml
- 对于此类型Sql语句:WHERE id=1 OR id=10 OR id=16
- 在查询条件中,查询条件定义成一个sql片段,需要修改sql片段。
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=null and userCustom.sex!=‘‘">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=null and userCustom.username!=‘‘">
and user.username LIKE ‘%${userCustom.username}%‘
</if>
<if test="ids!=null">
<!-- 使用 foreach遍历传入ids
collection:指定输入 对象中集合属性
item:每个遍历生成对象中
open:开始遍历时拼接的串
close:结束遍历时拼接的串
separator:遍历的两个对象中需要拼接的串
-->
<!-- 使用实现下边的sql拼接:
AND (id=1 OR id=10 OR id=16)
-->
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="or">
<!-- 每个遍历需要拼接的串 -->
id=#{user_id}
</foreach>
<!-- 实现 “ and id IN(1,10,16)”拼接 -->
<!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
每个遍历需要拼接的串
#{user_id}
</foreach> -->
</if>
</if>
</sql>
测试代码
在UserQueryVo中:
public class UserQueryVo {
//传入多个id
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
- 对于另外一个sql的实现:
<!-- 实现 “ and id IN(1,10,16)”拼接 -->
<!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
每个遍历需要拼接的串
#{user_id}
</foreach> -->
以上是关于MyBatis入门第2天--MyBatis基础知识的主要内容,如果未能解决你的问题,请参考以下文章