Mybatis学习笔记
MyBatis的简介
Mybatis历史
- MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github
- iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
原始jdbc操作的分析
原始jdbc开发存在的问题如下:
-
数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能
-
sql 语句在代码中硬编码,耦合度高,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变java代码。
-
查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到 sql 语句的占位符位置
应对上述问题给出的解决方案:
- 使用数据库连接池初始化连接资源
- 将sql语句抽取到xml配置文件中
- 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
什么是MyBatis?
- MyBatis是一个优秀的基于java的持久层框架,它内部封装了 jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
- MyBatis通过xml或注解的方式将要执行的各种 statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。
- 最后MyBatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了 封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
- MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
MyBatis参考文档:https://mybatis.org/mybatis-3/zh/index.html
MyBatis的快速入门
MyBatis开发步骤:
- 添加MyBatis的坐标
- 创建user数据表
- 编写User实体类
- 编写映射文件UserMapper.xml
- 编写核心文件SqlMapConfig.xml
环境搭建
1、导入MyBatis的坐标和其他相关坐标
org.mybatis
mybatis
x.x.x
mysql
mysql-connector-java
8.0.23
junit
junit
4.12
log4j
log4j
1.2.17
2、创建user数据表
3、创建User实体类
public class User {
private int id;
private String username;
private String password;
// 省略set,get方法
}
4、编写UserMapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
5、编写MyBatis核心文件
习惯上命名为
mybatis-config.xml
,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息,核心配置文件存放的位置是src/main/resources目录下
<?xml version="1.0" encoding="UTF-8" ?>
编写测试代码
以测试查询list为例:
// 加载核心配置文件(路径相对于resources目录)
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行sql语句 参数:namespace+id
List userList = sqlSession.selectList("userMapper.findAll");
// 打印结果
System.out.println(userList);
// 释放资源
sqlSession.close();
加入log4j日志功能
1.加入依赖
log4j
log4j
1.2.17
加入log4j的配置文件
-
log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下
-
日志的级别:FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细
-
<?xml version="1.0" encoding="UTF-8" ?>
MyBatis的映射文件概述
- 相关概念:ORM(Object Relationship Mapping)对象关系映射。
- 对象:Java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段/列 |
对象 | 记录/行 |
- 映射文件的命名规则
- 表所对应的实体类的类名+Mapper.xml
- 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
- 因此一个映射文件对应一个实体类,对应一张表的操作
- MyBatis映射文件用于编写SQL,访问以及操作表中的数据
- MyBatis映射文件存放的位置是src/main/resources/mappers目录下
- MyBatis中可以面向接口操作数据,要保证两个一致
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
MyBatis的增删改查操作
插入操作
编写UserMapper.xml
insert into user values(#{id},#{username},#{password})
注意区分
${}
和#{}
${}
是 Properties ?件中的变量占位符,它可以?于标签属性值和 sql 内部,属于静态?本替换,?如${driver}
会被静态替换为com.mysql.cj.jdbc.Driver
。
#{}
是 sql 的参数占位符,Mybatis 会将 sql 中的#{}
替换为?
号,在 sql 执?前会使?PreparedStatement 的参数设置?法,按序给 sql 的?
号占位符设置参数值,?如ps.setInt(0, parameterValue)
,#{item.name}
的取值?式为使?反射从参数对象中获取,item 对象的 name 属性值,相当于param.getItem().getName()
。
编写插入实体User的代码
@Test
//插入操作
public void test2() throws IOException {
//模拟user对象
User user = new User();
user.setUsername("xxxx");
user.setPassword("abc");
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("userMapper.save", user);
//mybatis执行更新操作 提交事务
sqlSession.commit();
sqlSession.close();
}
插入操作注意问题
- 插入语句使用insert标签
- 在映射文件中使用
parameterType
属性指定要插入的数据类型 - Sql语句中使用
#{实体属性名}
方式引用实体中的属性值 - 插入操作使用的API是
sqlSession.insert(“命名空间.id”,实体对象);
- 插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即
sqlSession.commit()
删除操作
编写UserMapper.xml
delete from user where id=#{id}
编写删除数据的代码
@Test
//删除操作
public void test4() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("userMapper.delete", 8);
sqlSession.commit();
sqlSession.close();
}
删除操作注意问题
- 删除语句使用delete标签
- Sql语句中使用#{任意字符串}方式引用传递的单个参数
- 删除操作使用的API是
sqlSession.delete(“命名空间.id”,Object);
查询操作
编写UserMapper.xml
单个查询和列表查询
@Test
//查询一个对象
public void test5() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("userMapper.findById", 1);
System.out.println(user);
sqlSession.close();
}
@Test
//查询列表
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List userList = sqlSession.selectList("userMapper.findAll");
System.out.println(userList);
sqlSession.close();
}
- 查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系
- resultType:自动映射,用于属性名和表中字段名一致的情况
- resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
- 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值
修改操作
update user set username=#{username},password=#{password} where id=#{id}
@Test
//修改操作
public void test3() throws IOException {
//模拟user对象
User user = new User();
user.setId(7);
user.setUsername("lucy");
user.setPassword("123");
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行操作 参数:namespace+id
sqlSession.update("userMapper.update", user);
sqlSession.commit();
sqlSession.close();
}
修改操作注意问题
- 修改语句使用update标签
- 修改操作使用的API是
sqlSession.update(“命名空间.id”,实体对象);
#{}和${}区别
- MyBatis获取参数值的两种方式:${}和#{}
- ${}的本质就是字符串拼接,#{}的本质就是占位符赋值
- ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
Mybatis获取参数的方式
单个字面量类型的参数
若mapper接口中的方法参数为单个的字面量类型,此时可以使用${}和#{}以任意的名称(最好见名识意)获取参数的值,注意${}需要手动加单引号
多个字面量类型的参数
- 若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中
以arg0,arg1...为键,以参数为值;
以param1,param2...为键,以参数为值
- 因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
- 使用arg或者param都行,要注意的是,arg是从arg0开始的,param是从param1开始的
map集合类型的参数
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
@Test
public void checkLoginByMap() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
Map map = new HashMap<>();
map.put("usermane","admin");
map.put("password","123456");
User user = mapper.checkLoginByMap(map);
System.out.println(user);
}
实体类型的参数
若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
@Test
public void insertUser() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
User user = new User(null,"Tom","123456",12,"男","123@321.com");
mapper.insertUser(user);
}
使用@Param标识参数
可以通过@Param注解标识mapper接口中的方法参数,此时,会将这些参数放在map集合中
- 以@Param注解的value属性值为键,以参数为值;
- 以param1,param2...为键,以参数为值;
- 只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
@Test
public void checkLoginByParam() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
mapper.CheckLoginByParam("admin","123456");
}
MyBatis的核心配置文件概述
MyBatis核心配置文件层级关系
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
MyBatis常用配置解析
1) environments标签
?xml version="1.0" encoding='UTF-8' ?>
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
数据源(dataSource)类型有三种:
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
- JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
2)mapper标签
该标签的作用是加载映射的,需要告诉 MyBatis 到哪里去找到这些sql语句。
加载方式有如下几种:
- 使用相对于类路径的资源引用,例如:
- 使用完全限定资源定位符(URL),例如:
- 使用映射器接口实现类的完全限定类名,例如:
- 将包内的映射器接口实现全部注册为映射器,例如:
3)properties标签
4)typeAliases标签
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_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 |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
MyBatis的相应API
SqlSession工厂构建器SqlSessionFactoryBuilder
常用API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
其中, Resources 工具类,这个类在 org.apache.ibatis.io
包中。Resources 类帮助你从类路径下、文件系统或 一个 web URL 中加载资源文件。
SqlSession工厂对象SqlSessionFactory
SqlSession openSession()
SqlSession openSession(boolean autoCommit) // 参数为是否自动提交,如果设置为true,那么不需要手动提交事务
默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:
- 事务作用域将会开启(也就是不自动提交)。
- 将由当前环境配置的 DataSource 实例中获取 Connection 对象。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
SqlSession会话对象
SqlSession 在 MyBatis 中是非常强大的一个类。它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。
语句执行方法
这些方法被用来执行定义在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。你可以通过名字快速了解它们的作用,每一方法都接受语句的 ID 以及参数对象,参数可以是原始类型(支持自动装箱或包装类)、JavaBean、POJO 或 Map。
T selectOne(String statement, Object parameter)
List selectList(String statement, Object parameter)
Cursor selectCursor(String statement, Object parameter)
Map selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象或 null 值。如果返回值多于一个,就会抛出异常。如果你不知道返回对象会有多少,请使用 selectList。如果需要查看某个对象是否存在,最好的办法是查询一个 count 值(0 或 1)。selectMap 稍微特殊一点,它会将返回对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多个结果集转为 Map 类型值。由于并不是所有语句都需要参数,所以这些方法都具有一个不需要参数的重载形式。
事务控制方法
有四个方法用来控制事务作用域。当然,如果你已经设置了自动提交或你使用了外部事务管理器,这些方法就没什么作用了。然而,如果你正在使用由 Connection 实例控制的 JDBC 事务管理器,那么这四个方法就会派上用场:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新或删除方法改变了数据库。如果你没有使用这些方法提交修改,那么你可以在 commit 和 rollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。
确保 SqlSession 被关闭
void close()
对于你打开的任何 session,你都要保证它们被妥善关闭,这很重要。保证妥善关闭的最佳代码模式是这样的:
SqlSession session = sqlSessionFactory.openSession();
try (SqlSession session = sqlSessionFactory.openSession()) {
// 假设下面三行代码是你的业务逻辑
session.insert(...);
session.update(...);
session.delete(...);
session.commit();
}
代理开发方式介绍
采用Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
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层(Mapper层)中只定义了接口
public interface UserMapper {
public List findAll() throws IOException;
public User findById(int id);
}
public class ServiceDemo {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得MyBatis框架生成的UserMapper接口的实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List all = mapper.findAll();
System.out.println(all);
User user = mapper.findById(1);
System.out.println(user);
}
}
动态SQL语句
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。
当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)
测试代码:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//模拟ids的数据
List ids = new ArrayList();
ids.add(1);
ids.add(2);
List userList = mapper.findByIds(ids);
System.out.println(userList);
out:
18:51:26,142 DEBUG JdbcTransaction:137 - Opening JDBC Connection
18:51:26,303 DEBUG PooledDataSource:406 - Created connection 959629210.
18:51:26,303 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3932c79a]
18:51:26,305 DEBUG findByIds:159 - ==> Preparing: select * from user WHERE id in( ? , ? )
18:51:26,333 DEBUG findByIds:159 - ==> Parameters: 1(Integer), 2(Integer)
18:51:26,364 DEBUG findByIds:159 - <== Total: 2
[User{id=1, username='tom', password='123456'}, User{id=2, username='jerry', password='123456'}]
foreach标签的属性含义如下:
标签用于遍历集合,它的属性:
- collection:代表要遍历的集合元素,注意编写时不要写
#{}
- open:代表语句的开始部分
- close:代表结束部分
- item:代表遍历集合的每个元素,生成的变量名
- sperator:代表分隔符
SQL语句抽取
可将重复的 sql 提取出来,使用时用
引用即可,最终达到 sql 重用的目的
标签:sql片段抽取
将上面的语句改成:
select * from user
类型处理器(typeHandlers)
官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
默认的类型处理器无需设置!
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler |
java.lang.Boolean , boolean |
数据库兼容的 BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short , short |
数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler |
java.lang.Integer , int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long , long |
数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler |
java.lang.Float , float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
提示 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
具体做法为:实现org.apache.ibatis.type.TypeHandler
接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler
, 并且可以(可选地)将它映射到一个 JDBC 类型。
需求举例:
一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的毫秒值(bigint)之间转换。
开发步骤:
1、定义转换类继承类BaseTypeHandler
2、覆盖4个未实现的方法,其中setNonNullParameter
为java程序设置数据到数据库的回调方法,getNullableResult
为查询时mysql的字符串类型转换成 java的Type类型的方法 3、在MyBatis核心配置文件中进行注册 4、测试转换是否正确
public class DateTypeHandler extends BaseTypeHandler {
// 将java类型转换成数据库需要的类型
// int i表示表中第几个字段
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
long time = date.getTime();
preparedStatement.setLong(i, time);
}
// 将数据库中类型转换成java类型
// String参数 要转换的字段名称
// ResultSet 查询出的结果集
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
// 获得结果集中需要的数据(long) 转换成Date类型 返回
long aLong = resultSet.getLong(s);
Date date = new Date(aLong);
return date;
}
// 将数据库中类型转换成java类型
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
long aLong = resultSet.getLong(i);
Date date = new Date(aLong);
return date;
}
// 将数据库中类型转换成java类型
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long aLong = callableStatement.getLong(i);
Date date = new Date(aLong);
return date;
}
}
public abstract long getLong(String columnLabel)
方法检索此ResultSet对象的当前行中指定列的值,作为Java编程语言中的long返回
在sqlMapConfig.xml
中注册自定义的类型处理器
测试代码:
@Test
public void test1() throws IOException {
... // 省略获取mapper
//创建user
User user = new User();
user.setUsername("ceshi1");
user.setPassword("abcdf");
user.setBirthday(new Date());
//执行保存操作
mapper.save(user);
sqlSession.commit();
sqlSession.close();
}
数据库中的数据
其中birthday字段以bigint方式存储
对刚插入的数据进行查询:
user中的birthday:Mon Mar 28 22:07:51 HKT 2022
返回的数据又被封装为Date类型
疑问:在表的设计中,birthday字段采用varchar和bigint都顺利完成了转换,为什么呢?
plugins标签(使用分页助手PageHelper)
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即 可获得分页的相关数据
开发步骤: 1、导入通用PageHelper的坐标 2、在mybatis核心配置文件中配置PageHelper插件 3、测试分页数据获取
1、导入通用PageHelper的坐标
com.github.pagehelper
pagehelper
3.7.5
test
com.github.jsqlparser
jsqlparser
0.9.1
2、在mybatis核心配置文件中配置PageHelper插件
3、测试分页代码实现
@Test
public void test3() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//设置分页相关参数 当前页+每页显示的条数
PageHelper.startPage(3,2); // 每页显示两条数据,获取第3页的数据
List userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
//获得与分页相关参数
PageInfo pageInfo = new PageInfo(userList);
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示条数:"+pageInfo.getPageSize());
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("上一页:"+pageInfo.getPrePage());
System.out.println("下一页:"+pageInfo.getNextPage());
System.out.println("是否是第一个:"+pageInfo.isIsFirstPage());
System.out.println("是否是最后一个:"+pageInfo.isIsLastPage());
sqlSession.close();
}
一对一查询
一对一查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
一对一查询的sql语句
sql查询语句(inner join):
select * from orders o,user u where o.uid=u.id;
查询结果:
创建Order和User实体
在创建Order和User实体时,不使用主键/外键,而使用【面向对象】的思想。
public class Order {
private int id;
private Date ordertime;
private double total;
//当前订单属于哪一个用户
private User user;
// 省略set,get方法
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
//描述的是当前用户存在哪些订单
private List orderList;
// 省略set,get方法
}
创建OrderMapper接口
public interface OrderMapper {
//查询所有的订单(包含订单的用户信息)
public List findAll();
}
配置OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
测试代码:
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List orderList = mapper.findAll();
for (Order order : orderList) {
System.out.println(order);
}
sqlSession.close();
}
out:
Order{id=1, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=3000.0, user=User{id=1, username='tom', password='123456', birthday=null, roleList=null}}
Order{id=2, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=5000.0, user=User{id=1, username='tom', password='123456', birthday=null, roleList=null}}
Order{id=3, ordertime=Sat Jan 01 00:00:00 HKT 2022, total=10000.0, user=User{id=2, username='jerry', password='123456', birthday=null, roleList=null}}
Order{id=4, ordertime=Wed Feb 02 00:00:00 HKT 2022, total=100.0, user=User{id=3, username='lucy', password='123', birthday=null, roleList=null}}
一对多查询
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
一对一查询的sql语句
sql查询语句(left join):
LEFT JOIN 关键字从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL。
select *,o.id oid from user u left join orders o on u.id=o.uid;
查询结果:
实体类沿用一对一查询中的User,Order
创建UserMapper接口:
public interface UserMapper {
public List findAll();
}
配置UserMapper.xml:
注:已在sqlMaoConfig.xml中设置了实体类的别名,user的id不应该使用列uid,左连接会出现null
查询代码:
List userList = mapper.findAll();
for (User user : userList) {
List orderList = user.getOrderList();
if (orderList.isEmpty()) {
continue;
}
System.out.println(user.getUsername());
for (Order order : orderList) {
System.out.println(order);
}
System.out.println("----------------------------------");
}
out:
tom
Order{id=1, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=3000.0, user=null}
Order{id=2, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=5000.0, user=null}
----------------------------------
jerry
Order{id=3, ordertime=Sat Jan 01 00:00:00 HKT 2022, total=10000.0, user=null}
----------------------------------
lucy
Order{id=4, ordertime=Wed Feb 02 00:00:00 HKT 2022, total=100.0, user=null}
多对多查询
多对多查询的模型
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用 多对多查询的需求:查询用户同时查询出该用户的所有角色
多对多查询的语句对应的sql语句:
SELECT *
FROM USER u, sys_user_role ur, sys_role r
WHERE u.id = ur.userId
AND ur.roleId = r.id;
查询的结果如下:
创建Role实体,修改User实体
public class User {
// ...
//描述的是当前用户具备哪些角色
private List roleList;
}
public class Role {
private int id;
private String roleName;
private String roleDesc;
}
添加UserMapper接口方法
List findAllUserAndRole();
配置UserMapper.xml:
测试代码:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List userAndRoleAll = mapper.findUserAndRoleAll();
for (User user : userAndRoleAll) {
System.out.println(user);
}
out:
User{id=1, username='tom', password='123456', birthday=null, orderList=null,
roleList=[Role{id=1, roleName='院长', roleDesc='负责全面工作'},
Role{id=2, roleName='研究员', roleDesc='课程研发工作'}]}
User{id=2, username='jerry', password='123456', birthday=null, orderList=null,
roleList=[Role{id=2, roleName='研究员', roleDesc='课程研发工作'},
Role{id=3, roleName='讲师', roleDesc='授课工作'}]}
MyBatis多表配置方式:
- 一对一配置:使用
做配置 - 一对多配置:使用
+
做配置 - 多对多配置:使用
+
做配置
MyBatis的常用注解
@Insert
:实现新增 @Update
:实现更新 @Delete
:实现删除 @Select
:实现查询 @Result
:实现结果集封装 @Results
:可以与@Result
一起使用,封装多个结果集 @One
:实现一对一结果集封装 @Many
:实现一对多结果集封装
MyBatis的增删改查
在增删查改的测试代码部分,可以统一获取 UserMapper 对象
注意这里的@Before
不是切面的注解,而是 junit 的注解。
@Before
– 表示在任意使用@Test注解标注的public void方法执行之前执行
public class MyBatisTest {
private UserMapper mapper;
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
mapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void testSave() {
User user = new User();
user.setUsername("tom");
user.setPassword("abc");
mapper.save(user);
}
@Test
public void testUpdate() {
User user = new User();
user.setId(18);
user.setUsername("lucy");
user.setPassword("123");
mapper.update(user);
}
@Test
public void testDelete() {
mapper.delete(18);
}
@Test
public void testFindById() {
User user = mapper.findById(2);
System.out.println(user);
}
@Test
public void testFindAll() {
List all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
}
}
修改MyBatis的核心配置文件,因为我们使用了注解替代的映射文件,所以只需要加载使用了注解的Mapper接口即可。
或者指定扫描包含映射关系的接口所在的包
不再使用userMapper.xml
增删查改的注解实现(配置在userMapper接口中):
public interface UserMapper {
// 插入
@Insert("insert into user values(#{id},#{username},#{password},#{birthday})")
public void save(User user);
// 修改
@Update("update user set username=#{username},password=#{password} where id=#{id}")
public void update(User user);
// 删除
@Delete("delete from user where id=#{id}")
public void delete(int id);
// 查一条
@Select("select * from user where id=#{id}")
public User findById(int id);
// 查列表
@Select("select * from user")
public List findAll();
}
MyBatis的注解实现复杂映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置
来实现,使用注解开发后,可以使用@Results注解 ,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
注解 | 说明 |
---|---|
@Results |
代替的是标签 ,该注解中可以使用单个@Result注解,也可以使用@Result集合。 使用格式:@Results({@Result(),@Result()}) 或@Results(@Result()) |
@Result |
代替了 标签和 标签 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用的@One注解 many:需要使用的@Many 注解 |
@One (一对一) |
代替了 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One注解属性介绍: select: 指定用来多表查询的 sqlmapper 使用格式:@Result(column=" ",property="",one=@One(select="")) |
@Many (多对一) |
代替了 标签, 是是多表查询的关键,在注解中用来指定子查询返回对象集合。 使用格式:@Result(property="",column="",many=@Many(select="")) |
一对一查询
查询一个订单,与此同时查询出该订单所属的用户
对应的sql语句(逻辑上不完全对应):
select * from orders;
select * from user where id=[查询出订单的uid];
使用注解配置OrderMapper接口
@Select("select * from orders")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "ordertime", property = "ordertime"),
@Result(column = "total", property = "total"),
@Result(
property = "user", //要封装的属性名称
column = "uid", //根据哪个字段去查询user表的数据
javaType = User.class, //要封装的实体类型
one = @One(select = "com.itheima.mapper.UserMapper.findById") //select属性 代表查询那个接口的方法获得数据
)
})
public List findAll();
只做一次查询的写法
@Select("select *,o.id oid from orders o,user u where o.uid=u.id")
@Results({
@Result(column = "oid",property = "id"),
@Result(column = "ordertime",property = "ordertime"),
@Result(column = "total",property = "total"),
@Result(column = "uid",property = "user.id"),
@Result(column = "username",property = "user.username"),
@Result(column = "password",property = "user.password")
})
public List findAll();
一对多查询
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
对应的sql语句:
select * from user;
select * from orders where uid=查询出用户的id;
使用注解配置UserMapper接口
@Select("select * from user")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(
property = "orderList",
column = "id",
javaType = List.class,
many = @Many(select = "com.itheima.mapper.OrderMapper.findByUid")
)
})
public List findUserAndOrderAll();
多对多查询
多对多查询的需求:查询用户同时查询出该用户的所有角色
对应的sql语句:
select * from user;
select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id
使用注解配置RoleMapper接口
@Select("SELECT * FROM sys_user_role ur,sys_role r WHERE ur.roleId=r.id AND ur.userId=#{uid}")
public List findByUid(int uid);
使用注解配置UserMapper接口
@Select("select * from user")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(
property = "roleList",
column = "id",
javaType = List.class,
many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid")
)
})
public List findUserAndRoleAll();
MybatisTools
public class MybatisTools {
private static SqlSessionFactory factory;
private MybatisTools(){}
static {
try {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public SqlSession getSession(){
return factory.openSession(true);
}
}