一 Mybatis简介 1、Mybatis简介
项目地址https://github.com/mybatis/mybatis-3/
MyBatis 是支持定制化 SQL、存储过程以及高级 映射的优秀的持久层框架。• MyBatis 避免了几乎所有的 JDBC 代码和手动设 置参数以及获取结果集• MyBatis可以使用简单的XML或注解用于配置和原 始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记 录、2、MyBatis历史 原是Apache的一个开源项目iBatis, 2010年6月这 个项目由Apache Software Foundation 迁移到了 Google Code,随着开发团队转投Google Code 旗下, iBatis3.x正式更名为MyBatis ,代码于 2013年11月迁移到GithubiBatis一词来源于“internet”和“abatis”的组合,是 一个基于Java的持久层框架。 iBatis提供的持久 层框架包括SQL Maps和Data Access Objects (DAO)
3.为什么要使用MyBatis? MyBatis是一个半自动化的持久化层框架。JDBC – SQL夹在Java代码块里,耦合度高导致硬编码内伤 – 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见 Hibernate和JPA
– 长难复杂SQL,对于Hibernate而言处理也不容易– 内部自动生产的SQL,不容易做特殊优化。– 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。 导致数据库性能下降。对开发人员而言,核心sql还是需要自己优化sql和java编码分开,功能边界清晰,一个专注业务、 一个专注数据
二 Mybatis hello world案例 1.HelloWorld简单版
创建一张测试表创建对应的javaBean创建mybatis配置文件,sql映射文件 – 测试
2.Mybatis操作数据库 2.1 创建MyBatis全局配置文件
MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深 的设置(settings)和属性(properties)信息、如数据 库连接池信息等。指导着MyBatis进行工作。我们可以 参照官方文件的配置示例。
2.2 创建SQL映射文件
映射文件的作用就相当于是定义Dao接口的实现类如何 工作。这也是我们使用MyBatis时编写的最多的文件。
mysql数据库中创建表employee和相应字段创建employee java类用来封装表里面的数据,类中字段和sql表中字段名称和类型统一。创建(全局配置文件)mybatis.config.xml文件配置相应的数据库连接信息<?xml version="1.0" encoding="UTF-8" ?>
编写测试类
sqlsession
public SqlSessionFactory getSqlSessionFactory() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);return new SqlSessionFactoryBuilder().build(inputStream);}
测试类
@Testpublic void test() throws IOException {// 2、获取sqlSession实例,能直接执行已经映射的sql语句// sql的唯一标识:statement Unique identifier matching the statement to use.// 执行sql要用的参数:parameter A parameter object to pass to the statement.SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();SqlSession openSession = sqlSessionFactory.openSession();try {Employee employee = openSession.selectOne("com.atguigu.mybatis.EmployeeMapper.selectEmp", 1);System.out.println(employee);} finally {openSession.close();}}
创建sql 的xml 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
2.3 hello world 接口式编程
创建一个Dao接口 接口有更强的参数类型ji
public interface EmployeeMapper {public Employee getEmpById(Integer id);}
修改Mapper文件测试
@Testpublic void test01() throws IOException {// 1、获取sqlSessionFactory对象SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();// 2、获取sqlSession对象SqlSession openSession = sqlSessionFactory.openSession();try {// 3、获取接口的实现类对象//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);Employee employee = mapper.getEmpById(1);System.out.println(mapper.getClass());System.out.println(employee);} finally {openSession.close();}}
接口和xml文件动态绑定 ,接口会自动创建的代理对象
2.4 hello world 案例小结 1、接口式编程
原生: Dao ====> DaoImpl
mybatis: Mapper ====> xxMapper.xml
2、SqlSession代表和数据库的一次会话;用完必须关闭;
3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
SqlSession非线程安全就不可以写成成员变量,多线程环境下 多个线程使用不安全
4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
(将接口和xml进行绑定)
EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
5、两个重要的配置文件:
mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息
sql映射文件:保存了每一个sql语句的映射信息:
将sql抽取出来。
三 Mybatis全局配置文件
1、接口式编程
原生: Dao ====> DaoImpl
mybatis: Mapper ====> xxMapper.xml
2、SqlSession代表和数据库的一次会话;用完必须关闭;
3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
SqlSession非线程安全就不可以写成成员变量,多线程环境下 多个线程使用不安全
4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
(将接口和xml进行绑定)
EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
5、两个重要的配置文件:
mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息
sql映射文件:保存了每一个sql语句的映射信息:
将sql抽取出来。
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的 设置(settings)和属性(properties)信息。
dtd文件 是约束文件 可以提供标签提示
所有依规定蓝色配置都在配置文件configuration.class 中 1.properties属性 1、mybatis可以使用properties来引入外部properties配置文件的内容;
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
2.settings设置
1、mybatis可以使用properties来引入外部properties配置文件的内容;
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
2.settings设置
是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
settings包含很多重要的设置项
setting:用来设置每一个设置项
name:设置项名
value:设置项取值
3.typeAliases别名处理器
类型别名是为 Java 类型设置一个短的名字,可以方便我们 引用某个类,全类名可以写别名
typeAliases:别名处理器:可以为我们的java类型起别名,别名不区分大小写
typeAlias:为某个java类型起别名
type:指定要起别名的类型全类名;默认别名就是类名小写;employee
alias:指定新的别名
package:为某个包下的所有类批量起别名
name:指定包名(为当前包以及下面所有的后代包的每一个类都起一个默认别名(类名小写),)
批量起别名的情况下,别名冲突(多个包,子包),使用@Alias注解为某个类型指定新的别名
值得注意的是,MyBatis已经为许多常见的 Java 类型内建 了相应的类型别名。它们都是大小写不敏感的,我们在起 别名的时候千万不要占用已有的别名。
4.typeHandlers类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中 设置一个参数时,还是从结果集中取出一个值时, 都会 用类型处理器将获取的值以合适的方式转换成 Java 类型(数据库类型和java类型映射)。
日期类型的处理
日期和时间的处理,JDK1.8以前一直是个头疼的 问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实 现全部的JSR310规范了。日期时间处理上,我们可以使用MyBatis基于 JSR310(Date and Time API)编写的各种日期 时间类型处理器。MyBatis3.4以前的版本需要我们手动注册这些处 理器,以后的版本都是自动注册的
自定义类型处理器
我们可以重写类型处理器或创建自己的类型处理 器来处理不支持的或非标准的类型。
• 步骤:
实现org.apache.ibatis.type.TypeHandler接口或 者继承org.apache.ibatis.type.baseTypeHandler指定其映射某个JDBC类型(可选操作)在mybatis全局配置文件中注册
5.plugins插件
插件是MyBatis提供的一个非常强大的机制,我们 可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何 一个方法的执行。
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler (prepare, parameterize, batch, update, query)
6.environments环境 MyBatis可以配置多种环境,比如开发、测试和生 产环境需要有不同的配置每种环境使用一个environment标签进行配置并指 定唯一标识符可以通过environments标签中的default属性指定 一个环境的标识符来快速的切换环境
MyBatis可以配置多种环境,比如开发、测试和生 产环境需要有不同的配置每种环境使用一个environment标签进行配置并指 定唯一标识符可以通过environments标签中的default属性指定 一个环境的标识符来快速的切换环境
environments:环境,mybatis可以配置多种环境 ,default指定使用某种环境。可以达到快速切换环境。
environment指定具体环境
配置一个具体的环境信息;必须有两个标签;id代表当前环境的唯一标识
id:指定当前环境的唯一标识transactionManager、和dataSource都必须有
transactionManager 事务管理器
type 事务管理器的类型: JDBC(JdbcTransactionFactory)|MANAGED(ManagedTransactionFactory) | 自定义
JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数 据源得到的连接来管理事务范围。 JdbcTransactionFactory MANAGED:不提交或回滚一个连接、让容器来管理 事务的整个生命周期(比如 JEE 应用服务器的上下 文)。 ManagedTransactionFactory自定义:自定义事务管理器:实现TransactionFactory接口.type指定为全类名 ,type=全类名/ 别名
dataSource 数据源
type 数据源类型: UNPOOLED(UnpooledDataSourceFactory) | POOLED (PooledDataSourceFactory) | JNDI (JndiDataSourceFactory) | 自定义
UNPOOLED:不使用连接池, UnpooledDataSourceFactoryPOOLED:使用连接池, PooledDataSourceFactoryJNDI: 在EJB 或应用服务器这类容器中查找指定的数 据源自定义:实现DataSourceFactory接口,type是全类名 定义数据源的 获取方式
实际开发中我们使用Spring管理数据源,并进行 事务控制的配置来覆盖上述配
7.databaseIdProvider环境
MyBatis 可以根据不同的数据库厂商执行不同的语句(移植性支持)
databaseIdProvider:支持多数据库厂商的;
type="DB_VENDOR":VendorDatabaseIdProvider
作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),mybatis就能根据数据库厂商标识来执行不同的sql;
MySQL,Oracle,SQL Server,xxxx
Type: DB_VENDOR
使用MyBatis提供的VendorDatabaseIdProvider解析数据库 厂商标识。也可以实现DatabaseIdProvider接口来自定义。Property-name:数据库厂商标识Property-value:为标识起一个别名,方便SQL语句使用 databaseId属性引用
DB_VENDOR
会通过 DatabasemetaData#getDatabaseProductName() 返回的字符 串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不 同版本会返回不同的值,所以最好通过设置属性别名来使其变短
MyBatis匹配规则如下
如果没有配置databaseIdProvider标签,那么databaseId=null如果配置了databaseIdProvider标签,使用标签配置的name去匹 配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为 null如果databaseId不为null,他只会找到配置databaseId的sql语句MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃
8.mapper映射
mapper逐个注册SQL映射文件,将sql映射注册到全局配置中
将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中
mapper:注册一个sql映射
注册配置文件
resource:引用类路径下的sql映射文件
mybatis/mapper/EmployeeMapper.xml
url:引用网路路径或者磁盘路径下的sql映射文件
file:///var/mappers/AuthorMapper.xml
或者使用批量注册:
这种方式要求SQL映射文件名必须和接口名相同并且在同一目录
注册接口
class:引用(注册)接口,
1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
2、没有sql映射文件,所有的sql都是利用注解写在接口上;
@Select("select * from tbl_employee where id=#{id}")public Employee getEmpById(Integer id);
推荐:
比较重要的,复杂的Dao接口我们来写sql映射文件
不重要,简单的Dao接口为了开发快速可以使用注解;
本章小结 每个标签都有先后顺序 标签有提示
四 MyBatis-映射文件 1.映射文件sql语句xml
映射文件(sql的xml文件)指导着MyBatis如何进行数据库增删改查(所有sql语句都在xml文件里面编写), 有着非常重要的意义
cache –命名空间的二级缓存配置cache-ref – 其他命名空间缓存配置的引用。resultMap – 自定义结果集映射parameterMap – 已废弃!老式风格的参数映射sql –抽取可重用语句块。insert – 映射插入语句update – 映射更新语句delete – 映射删除语句select – 映射查询语句
public Long addEmp(Employee employee);public boolean updateEmp(Employee employee);public void deleteEmpById(Integer id);
insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender}where id=#{id} delete from tbl_employee where id=#{id}
@Testpublic void test03() throws IOException{SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();//1、获取到的SqlSession不会自动提交数据SqlSession openSession = sqlSessionFactory.openSession();try{EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);//测试添加Employee employee = new Employee(null, "jerry4",null, "1");mapper.addEmp(employee);System.out.println(employee.getId());//测试修改//Employee employee = new Employee(1, "Tom", "jerry", "0");//boolean updateEmp = mapper.updateEmp(employee);//System.out.println(updateEmp);//测试删除//mapper.deleteEmpById(2);//2、手动提交数据openSession.commit();}finally{openSession.close();}}
mybatis允许增删改直接定义以下类型返回值
Integer、Long、Boolean、void
我们需要手动提交数据
sqlSessionFactory.openSession();===》手动提交sqlSessionFactory.openSession(true);===》自动提交
2.主键生成方式 2.1支持自增主键的数据库
若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
获取自增主键的值:
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys="true";使用自增主键获取主键值策略
keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
2.2 不支持自增主键的数据库
对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:
selectKey 元素将会首先运行,id 会被设置,然 后插入语句会被调用
获取非自增主键的值:
Oracle不支持自增;Oracle使用序列来模拟自增;
每次插入的数据的主键是从序列中拿到的值;如何获取到这个值
keyProperty:查出的主键值封装给javaBean的哪个属性
order="BEFORE":当前sql在插入sql之前运行
AFTER:当前sql在插入sql之后运行
resultType:查出的数据的返回值类型
BEFORE运行顺序:
先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
在运行插入的sql;就可以取出id属性对应的值
AFTER运行顺序:
先运行插入的sql(从序列中取出新值作为id);
再运行selectKey查询id的sql;
运行多条sql时获取是最后一条主键值
select EMPLOYEES_SEQ.nextval from dual insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) values(#{id},#{lastName},#{email})
3、参数(Parameters)传递 3.1 mybatis参数处理 单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况 MyBatis可直接使用这个参数,不需要经过任何处理
多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。 Map的key是param1,param2,0,1…,值就是参数的值。
命名参数
为参数使用@Param起一个名字,MyBatis就会将这些参数封 装进map中,key就是我们自己指定的名字
POJO
当这些参数属于我们业务POJO时,我们直接传递POJO
Map
我们也可以封装多个参数为map,直接传递
单个参数:mybatis不会做特殊处理,
#{参数名/任意名}:取出参数值
public Employee getEmpById(Integer id);
多个参数:mybatis会做特殊处理。
多个参数会被封装成 一个map,
key:param1...paramN,或者参数的索引也可以
value:传入的参数值
#{}就是从map中获取指定的key的值;
异常:
org.apache.ibatis.binding.BindingException:
Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
取值:#{id},#{lastName}
public Employee getEmpByIdAndLastName(@Param("id")Integer id,@Param("lastName")String lastName);
【命名参数】:明确指定封装参数时map的key;@Param("id")
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性
public Employee getEmp(Integer id,@Param("e")Employee emp);取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值
public Employee getEmpByMap(Map map);
TO:
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
Page{
int index;
int size;
}
3.2 参数处理
mybatis怎么处理参数
public Employee getEmp(@Param("id")Integer id,String lastName);
取值:id==>#{id/param1} lastName==>#{param2}
public Employee getEmp(Integer id,@Param("e")Employee emp);
取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
##特别注意:如果是Collection(List、Set)类型或者是数组,
也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list)
数组(array)
public Employee getEmpById(List ids);
取值:取出第一个id的值: #{list[0]}
参数封装源码分析
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;#{key}就可以取出map中的值;(@Param("id")Integer id,@Param("lastName")String lastName);ParamNameResolver解析参数封装map的;//1、names:{0=id, 1=lastName};构造器的时候就确定好了确定流程:1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)name的值:标注了param注解:注解的值没有标注:1.全局配置:useActualParamName(jdk1.8):name=参数名2.name=map.size();相当于当前元素的索引{0=id, 1=lastName,2=2}args【1,"Tom",'hello'】:public Object getNamedParams(Object[] args) { final int paramCount = names.size(); //1、参数为null直接返回 if (args == null || paramCount == 0) { return null; //2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回 } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; //3、多个元素或者有Param标注 } else { final Map param = new ParamMap
3.3 参数的获取 #{}:可以获取map中的值或者pojo对象属性的值;
${}:可以获取map中的值或者pojo对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=2 and last_name=?
区别:
#{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中;会有安全问题;
大多情况下,我们去参数的值都应该去使用#{};
原生jdbc不支持占位符的地方我们就可以使用${}进行取值
比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更丰富的用法:
规定参数的一些规则:
javaType、 jdbcType、 mode(存储过程)、 numericScale、
resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);
jdbcType通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;两种办法
1、#{email,jdbcType=OTHER};
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) values(#{id},#{lastName},#{email})
2、jdbcTypeForNull=NULL
3.4 select元素 Select元素来定义查询操作。Id:唯一标识符。
用来引用这条语句,需要和接口的方法名一致
parameterType:参数类型。
可以不传,MyBatis会根据TypeHandler自动推断
resultType:返回值类型。
别名或者全类名,如果返回的是集合,定义集合中元 素的类型。不能和resultMap同时使用
select 返回list集合
mybatis 会把要返回的数据封装再对象中放在集合中返回
public List getEmpsByLastNameLike(String lastName);
List like = mapper.getEmpsByLastNameLike("%e%");for (Employee employee : like) {System.out.println(employee);}
select 返回map集合
返回一条记录的map;Map key就是列名(对象中的属性值),值就是对应的值 //返回一条记录的map;key就是列名,值就是对应的值public Map getEmpByIdReturnMap(Integer id);
resultType 是map中object mybatis中起了别名 可以写map类型
多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBean //多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBeanpublic Map getEmpByLastNameLikeReturnMap(String lastName);
若是像让map的key为返回数据的主键,需要在接口上加上注解
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key@MapKey("id") //MapKey("lastName") 其他属性也可以public Map getEmpByLastNameLikeReturnMap(String lastName);
3.5 自动映射(自动封装) resultMap
全局setting设置
autoMappingBehavior默认是PARTIAL,开启自动映射 的功能。唯一的要求是列名和javaBean属性名一致 如果autoMappingBehavior设置为null则会取消自动映射 数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN -> aColumn,我们可以开启自动驼峰命名规 则映射功能,mapUnderscoreToCamelCase=true。自定义resultMap,实现高级结果集映射。
resultMap 内标签
constructor - 类在实例化时, 用来注入结果到构造方法中
idArg - ID 参数; 标记结果作为 ID 可以帮助提高整体效能
arg - 注入到构造方法的一个普通结果
id – 一个 ID 结果; 标记结果作为 ID 可以帮助提高整体效能result – 注入到字段或 JavaBean 属性的普通结果association – 一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集
嵌入结果映射 – 结果映射自身的集,或者参考一个
discriminator – 使用结果值来决定使用哪个结果映射
case – 基于某些值的结果映射
嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元 素,或者它可以参照一个外部的结果映射。 id & resul
public Employee getEmpById(Integer id);
resultMap 和resultTYPE只能二选一
自定义某个javaBean的封装规则
type:自定义规则的Java类型
id:唯一id方便引用
id & result
id 和 result 映射一个单独列的值到简单数据类型 (字符串,整型,双精度浮点数,日期等)的属性或字段
3.6 association 查询的数据中有另一个表的数据时
如 员工和部门一个员工对应一个部门
查询Employee的同时查询员工对应的部门Employee===Department一个员工有与之对应的部门信息;id last_name gender d_id did dept_name (private Department dept;)
public class Department {private Integer id;private String departmentName;private List emps;
员工类中有部门对象 一对一
public class Employee {private Integer id;private String lastName;private String email;private String gender;private Department dept;
查询语句
方式一 结果集映射
方式二 association
association 分步查询
使用association进行分步查询:
1、先按照员工id查询员工信息
2、根据查询员工信息中的d_id值去部门表查出部门信息
3、部门设置到员工中;
public Employee getEmpByIdStep(Integer id);
public interface DepartmentMapper {public Department getDeptById(Integer id);}
association 引入部门xmlsql语句
association 懒加载(延迟加载)
可以使用延迟加载(懒加载);(按需加载)
Employee==>Dept:
我们每次查询Employee对象的时候,都将一起查询出来。
部门信息在我们使用的时候再去查询;
分段查询的基础之上加上两个配置
lazyLoadingEnabled 懒加载 使用时加载
aggressiveLazyLoading 侵入加载 需要时全部加载 禁用按需加载
使用时就会查询出来 不使用时不查询
System.out.println(employee);//System.out.println(employee.getDept());System.out.println(employee.getDept());
3.7 Collection 一个部门对应多个员工
查询部门的时候将部门对应的所有员工信息也查询出来:注释在DepartmentMapper.xml中
部门类有员工集合 一对多
public class Department {private Integer id;private String departmentName;private List emps;
public Department getDeptByIdPlus(Integer id);
部门sql xml 中
嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则
分段查询
在部门sql xml里面定义 引入员工数据查询
public interface DepartmentMapper {public Department getDeptByIdStep(Integer id);}
public interface EmployeeMapperPlus {public List getEmpsByDeptId(Integer deptId);}
扩展:多列的值传递过去:
将多列的值封装map传递;
column="{key1=column1,key2=column2}"
fetchType="lazy":表示使用延迟加载;
- lazy:延迟
- eager:立即
3.8 鉴别器
鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
封装Employee:
如果查出的是女生:就把部门信息查询出来,否则不查询;
如果是男生,把last_name这一列的值赋值给email;
五 MyBatis-动态SQL
四 MyBatis-映射文件 1.映射文件sql语句xml
映射文件(sql的xml文件)指导着MyBatis如何进行数据库增删改查(所有sql语句都在xml文件里面编写), 有着非常重要的意义
cache –命名空间的二级缓存配置cache-ref – 其他命名空间缓存配置的引用。resultMap – 自定义结果集映射parameterMap – 已废弃!老式风格的参数映射sql –抽取可重用语句块。insert – 映射插入语句update – 映射更新语句delete – 映射删除语句select – 映射查询语句
public Long addEmp(Employee employee);public boolean updateEmp(Employee employee);public void deleteEmpById(Integer id);
@Testpublic void test03() throws IOException{SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();//1、获取到的SqlSession不会自动提交数据SqlSession openSession = sqlSessionFactory.openSession();try{EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);//测试添加Employee employee = new Employee(null, "jerry4",null, "1");mapper.addEmp(employee);System.out.println(employee.getId());//测试修改//Employee employee = new Employee(1, "Tom", "jerry", "0");//boolean updateEmp = mapper.updateEmp(employee);//System.out.println(updateEmp);//测试删除//mapper.deleteEmpById(2);//2、手动提交数据openSession.commit();}finally{openSession.close();}}
mybatis允许增删改直接定义以下类型返回值
Integer、Long、Boolean、void
我们需要手动提交数据
sqlSessionFactory.openSession();===》手动提交sqlSessionFactory.openSession(true);===》自动提交
2.主键生成方式 2.1支持自增主键的数据库
若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
获取自增主键的值:
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys="true";使用自增主键获取主键值策略
keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
2.2 不支持自增主键的数据库
对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:
selectKey 元素将会首先运行,id 会被设置,然 后插入语句会被调用
获取非自增主键的值:
Oracle不支持自增;Oracle使用序列来模拟自增;
每次插入的数据的主键是从序列中拿到的值;如何获取到这个值keyProperty:查出的主键值封装给javaBean的哪个属性
order="BEFORE":当前sql在插入sql之前运行
AFTER:当前sql在插入sql之后运行
resultType:查出的数据的返回值类型
BEFORE运行顺序:
先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
在运行插入的sql;就可以取出id属性对应的值
AFTER运行顺序:
先运行插入的sql(从序列中取出新值作为id);
再运行selectKey查询id的sql;运行多条sql时获取是最后一条主键值
3、参数(Parameters)传递 3.1 mybatis参数处理 单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况 MyBatis可直接使用这个参数,不需要经过任何处理
多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。 Map的key是param1,param2,0,1…,值就是参数的值。
命名参数
为参数使用@Param起一个名字,MyBatis就会将这些参数封 装进map中,key就是我们自己指定的名字
POJO
当这些参数属于我们业务POJO时,我们直接传递POJO
Map
我们也可以封装多个参数为map,直接传递
单个参数:mybatis不会做特殊处理,
#{参数名/任意名}:取出参数值
public Employee getEmpById(Integer id);
多个参数:mybatis会做特殊处理。
多个参数会被封装成 一个map,
key:param1...paramN,或者参数的索引也可以
value:传入的参数值
#{}就是从map中获取指定的key的值;
异常:
org.apache.ibatis.binding.BindingException:
Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
取值:#{id},#{lastName}
public Employee getEmpByIdAndLastName(@Param("id")Integer id,@Param("lastName")String lastName);
【命名参数】:明确指定封装参数时map的key;@Param("id")
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性
public Employee getEmp(Integer id,@Param("e")Employee emp);取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值
public Employee getEmpByMap(Map map);
TO:
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
Page{
int index;
int size;
}
3.2 参数处理
单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况 MyBatis可直接使用这个参数,不需要经过任何处理
多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。 Map的key是param1,param2,0,1…,值就是参数的值。
命名参数
为参数使用@Param起一个名字,MyBatis就会将这些参数封 装进map中,key就是我们自己指定的名字
POJO
当这些参数属于我们业务POJO时,我们直接传递POJO
Map
我们也可以封装多个参数为map,直接传递
单个参数:mybatis不会做特殊处理,
#{参数名/任意名}:取出参数值public Employee getEmpById(Integer id);
多个参数:mybatis会做特殊处理。
多个参数会被封装成 一个map,
key:param1...paramN,或者参数的索引也可以
value:传入的参数值
#{}就是从map中获取指定的key的值;异常:
org.apache.ibatis.binding.BindingException:
Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
取值:#{id},#{lastName}public Employee getEmpByIdAndLastName(@Param("id")Integer id,@Param("lastName")String lastName);
【命名参数】:明确指定封装参数时map的key;@Param("id")
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性public Employee getEmp(Integer id,@Param("e")Employee emp);取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值public Employee getEmpByMap(Map
map);
TO:
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
Page{
int index;
int size;
}
3.2 参数处理
mybatis怎么处理参数
public Employee getEmp(@Param("id")Integer id,String lastName);
取值:id==>#{id/param1} lastName==>#{param2}public Employee getEmp(Integer id,@Param("e")Employee emp);
取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}##特别注意:如果是Collection(List、Set)类型或者是数组,
也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list)
数组(array)
public Employee getEmpById(Listids);
取值:取出第一个id的值: #{list[0]}
参数封装源码分析
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;#{key}就可以取出map中的值;(@Param("id")Integer id,@Param("lastName")String lastName);ParamNameResolver解析参数封装map的;//1、names:{0=id, 1=lastName};构造器的时候就确定好了确定流程:1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)name的值:标注了param注解:注解的值没有标注:1.全局配置:useActualParamName(jdk1.8):name=参数名2.name=map.size();相当于当前元素的索引{0=id, 1=lastName,2=2}args【1,"Tom",'hello'】:public Object getNamedParams(Object[] args) { final int paramCount = names.size(); //1、参数为null直接返回 if (args == null || paramCount == 0) { return null; //2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回 } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; //3、多个元素或者有Param标注 } else { final Map
param = new ParamMap
3.3 参数的获取
#{}:可以获取map中的值或者pojo对象属性的值;
${}:可以获取map中的值或者pojo对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing: select * from tbl_employee where id=2 and last_name=?
区别:
#{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中;会有安全问题;
大多情况下,我们去参数的值都应该去使用#{};
原生jdbc不支持占位符的地方我们就可以使用${}进行取值
比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更丰富的用法:
规定参数的一些规则:
javaType、 jdbcType、 mode(存储过程)、 numericScale、
resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);jdbcType通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;两种办法
1、#{email,jdbcType=OTHER};insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) values(#{id},#{lastName},#{email})
2、jdbcTypeForNull=NULL
3.4 select元素
Select元素来定义查询操作。Id:唯一标识符。
用来引用这条语句,需要和接口的方法名一致
parameterType:参数类型。
可以不传,MyBatis会根据TypeHandler自动推断
resultType:返回值类型。
别名或者全类名,如果返回的是集合,定义集合中元 素的类型。不能和resultMap同时使用
select 返回list集合
mybatis 会把要返回的数据封装再对象中放在集合中返回
public List
getEmpsByLastNameLike(String lastName);
List
like = mapper.getEmpsByLastNameLike("%e%");for (Employee employee : like) {System.out.println(employee);}
select 返回map集合
返回一条记录的map;Map
key就是列名(对象中的属性值),值就是对应的值 //返回一条记录的map;key就是列名,值就是对应的值public Map
getEmpByIdReturnMap(Integer id); resultType 是map中object mybatis中起了别名 可以写map类型
多条记录封装一个map:Map
:键是这条记录的主键,值是记录封装后的javaBean //多条记录封装一个map:Map
:键是这条记录的主键,值是记录封装后的javaBeanpublic Map getEmpByLastNameLikeReturnMap(String lastName);
若是像让map的key为返回数据的主键,需要在接口上加上注解
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key@MapKey("id") //MapKey("lastName") 其他属性也可以public Map
getEmpByLastNameLikeReturnMap(String lastName);
3.5 自动映射(自动封装) resultMap
全局setting设置
autoMappingBehavior默认是PARTIAL,开启自动映射 的功能。唯一的要求是列名和javaBean属性名一致 如果autoMappingBehavior设置为null则会取消自动映射 数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN -> aColumn,我们可以开启自动驼峰命名规 则映射功能,mapUnderscoreToCamelCase=true。自定义resultMap,实现高级结果集映射。
resultMap 内标签
constructor - 类在实例化时, 用来注入结果到构造方法中
idArg - ID 参数; 标记结果作为 ID 可以帮助提高整体效能
arg - 注入到构造方法的一个普通结果
id – 一个 ID 结果; 标记结果作为 ID 可以帮助提高整体效能result – 注入到字段或 JavaBean 属性的普通结果association – 一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集
嵌入结果映射 – 结果映射自身的集,或者参考一个
discriminator – 使用结果值来决定使用哪个结果映射
case – 基于某些值的结果映射
嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元 素,或者它可以参照一个外部的结果映射。 id & resul
public Employee getEmpById(Integer id);
resultMap 和resultTYPE只能二选一
自定义某个javaBean的封装规则
type:自定义规则的Java类型
id:唯一id方便引用
3.6 associationid & result
id 和 result 映射一个单独列的值到简单数据类型 (字符串,整型,双精度浮点数,日期等)的属性或字段
3.7 Collection查询的数据中有另一个表的数据时
如 员工和部门一个员工对应一个部门
查询Employee的同时查询员工对应的部门Employee===Department一个员工有与之对应的部门信息;id last_name gender d_id did dept_name (private Department dept;)
public class Department {private Integer id;private String departmentName;private List
emps; 员工类中有部门对象 一对一
public class Employee {private Integer id;private String lastName;private String email;private String gender;private Department dept;
查询语句
方式一 结果集映射
方式二 association
association 分步查询
使用association进行分步查询:
1、先按照员工id查询员工信息
2、根据查询员工信息中的d_id值去部门表查出部门信息
3、部门设置到员工中;public Employee getEmpByIdStep(Integer id);
public interface DepartmentMapper {public Department getDeptById(Integer id);}
association 引入部门xmlsql语句
association 懒加载(延迟加载)
可以使用延迟加载(懒加载);(按需加载)
Employee==>Dept:
我们每次查询Employee对象的时候,都将一起查询出来。
部门信息在我们使用的时候再去查询;
分段查询的基础之上加上两个配置lazyLoadingEnabled 懒加载 使用时加载
aggressiveLazyLoading 侵入加载 需要时全部加载 禁用按需加载
使用时就会查询出来 不使用时不查询
System.out.println(employee);//System.out.println(employee.getDept());System.out.println(employee.getDept());
一个部门对应多个员工
查询部门的时候将部门对应的所有员工信息也查询出来:注释在DepartmentMapper.xml中
部门类有员工集合 一对多
public class Department {private Integer id;private String departmentName;private List
emps; public Department getDeptByIdPlus(Integer id);
部门sql xml 中
嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则
分段查询
在部门sql xml里面定义 引入员工数据查询
public interface DepartmentMapper {public Department getDeptByIdStep(Integer id);}
public interface EmployeeMapperPlus {public List
getEmpsByDeptId(Integer deptId);}
扩展:多列的值传递过去:
将多列的值封装map传递;
column="{key1=column1,key2=column2}"
fetchType="lazy":表示使用延迟加载;
- lazy:延迟
- eager:立即
3.8 鉴别器
五 MyBatis-动态SQL
鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
封装Employee:
如果查出的是女生:就把部门信息查询出来,否则不查询;
如果是男生,把last_name这一列的值赋值给email;