一. 简介
- mybatis的HelloWorld程序
- 创建全局配置文件 mybatis-config.xml ,添加数据源相关信息
- sql的映射文件 EmployeeMapper.xml
- 代码测试
//1.根据XML配置文件(全局配置文件)创建一个SqlSessionFactory对象 //2.sql映射文件,配置了每一个 sql,以及sql的封装规则 //3.将sql映射文件注册在全局配置配置文件中 //4.编写测试代码 // 1)根据全局配置文件得到sqlSessionFactory // 2)使用sqlSessionFactory得到SqlSession对象,来执行增删改查。一个SqlSession就代表一次会话。用完关闭 // 3)使用sql的唯一标识来告诉MyBatis执行那个sql。sql都保存在sql映射文件中。 @Test public void test() throws Exception { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try{ //@param statement Unique identifier matching the statement to use. SQL唯一标识 namespace+id //@param parameter A parameter object to pass to the statement. 参数 Employee employee = session.selectOne( "org.mybatis.example.EmployeeMapper.selectEmployee", 1); System.out.println(employee); } finally { session.close(); } }
- 接口式编程
- 首先写mapper 接口
public interface EmployeeMapper { public Employee getEmployeeById(Integer id); }
- 接口与SQL映射文件绑定(mybatis动态生成接口实现类)
SqlSession 代表和数据库的一次会话;用完必须关闭
SqlSession 和 connection 一样都是非线程安全的。每次使用都应该去获取新的对象。
mapper 接口没有实现类,但是mybatis会为这个接口生成一个代理对象。(将接口和xml绑定)
两个重要配置文件:mybatis全局配置文件:包含数据库连接池信息,事务管理等系统环境;sql映射文件:
二. MyBatis configuration 全局配置文件
properties:用来引入外部的数据源配置
settings
typeHandlers:将java类型和数据库类型进行适配。
- plugins:插件
MyBatis allows you to intercept calls to at certain points within the execution of a mapped statement.
By default, MyBatis allows plug-ins to intercept method calls of:- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 执行器
- ParameterHandler (getParameterObject, setParameters) 参数处理器
- ResultSetHandler (handleResultSets, handleOutputParameters) 结果集处理器
- StatementHandler (prepare, parameterize, batch, update, query) SQL语句处理器
environments:MyBatis can be configured with multiple environments.
environment:配置一个具体的环境信息,必须有两个标签,id代表当前环境的唯一标识。databaseIdProvider:MyBatis is able to execute different statements depending on your database vendor.
mappers:使用 class:直接注册接口,要能绑定成功,接口和SQL映射文件同名且同一目录
注:在maven项目中如果将 mapper.xml 放在 mapper 接口的包下面,需在pom文件中配置编译xml的指令
src/main/java **/*.xml
三. Mapper XML Files
select、insert 、update 、delete
insert into tb_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) update tb_employee set last_name = #{lastName},email=#{email},gender = #{gender} where id = #{id} delete from tb_employee where id = #{id} 单个参数处理:mybatis不会做特殊处理;#{参数名}:取出参数
多个参数处理
如果直接写多个形参,会报如下错误 org.apache.ibatis.binding.BindingException:
多个参数mybatis会做特殊处理,多个参数会被封装成一个map。key:param1.....paramN,或者索引;value:传入的参数值
通常使用 @Param("") 注解来指定参数名。如果多个参数是业务逻辑模型,可传入pojo,#{属性名} 直接取出。如果没有对应的pojo,不经常使用,可传入map。经常使用的话,可以编写TO数据传输对象。public Employee getEmpByIdAndLastName(@Param("id") Integer id,@Param("lastname") String lastname);
注意:如果是Collection(List、Set)类型或者是数组,也会做特殊处理,也是把传入的list或者数组封装在map中。key:Collection(collection)、如果是List 还可以使用 key(list),数组(array)
参数处理源码分析
mybatis使用ParamNameResolver 解析封装参数//构造方法 public ParamNameResolver(Configuration config, Method method) { //获取参数列表中每个参数的类型 final Class>[] paramTypes = method.getParameterTypes(); //获取参数列表上的注解 final Annotation[][] paramAnnotations = method.getParameterAnnotations(); //该集合用于记录参数索引与参数名称的对应关系 final SortedMap
map = new TreeMap<>(); //注解的个数 int paramCount = paramAnnotations.length; // get names from @Param annotations for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { //遍历方法所有参数 if (isSpecialParameter(paramTypes[paramIndex])) { //如果参数是RowBounds类型或ResultHandler类型,跳过对该参数的分析 // skip special parameters continue; } String name = null; //遍历该参数对应的注解集合 for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { //@Param 注解出现过一次,就将hasParamAnnotation初始化为true hasParamAnnotation = true; //获取Param注解指定的参数名称 name = ((Param) annotation).value(); break; } } //这个if代码解释了上面的实例中names集合的valu为什么是0和1 if (name == null) { // @Param was not specified. //该参数没有对应的@Param注解,则根据配置决定是否使用参数实际名作为其名称 if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { //使用参数的索引作为名称 // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); //记录保存到map } names = Collections.unmodifiableSortedMap(map); } //将实参与其对应名称进行关联 public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { //无参数,返回null return null; } else if (!hasParamAnnotation && paramCount == 1) { //未使用@param注解,只有一个参数 return args[names.firstKey()]; } else { //处理使用了@Param注解指定了参数名称或有多个参数的情况 //param这个map中记录了参数名称与实参之间的对应关系 final Map
param = new ParamMap<>(); int i = 0; for (Map.Entry entry : names.entrySet()) { //将参数名与实参对应关系记录到param中 param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) //为参数创建“param+索引”格式默认参数名称,如:param1,param2 等,并添加到param集合中 final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } } ${ xx } 和 #{ xx } 的区别:
#{ xx } :是以预编译的形式,将参数设置到sql语句中。
${ xx } :取出的值直接拼装在sql语句中。会有安全问题,不能防止sql注入。
大多数情况都应该使用 #{ xx } 取参数。原生JDBC不支持占位符的地方,可以使用${ xx } 取值。select * from ${year }_salary where xxx;
Select 记录封装成 Map
//多条记录封装成map,键是这条记录的主键,值是封装后的javabean //告诉mybatis封装map的时候,用哪个属性作为map的key @MapKey("id") Map
getEmpByLastNameReturnMap(String lastName); resultMap:自定义结果集映射规则
四. Dynamic SQL
if
choose (when, otherwise)
trim (where, set)
foreach
五. 缓存机制
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存。一级缓存和二级缓存。
- 默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
- 一级缓存
- 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
- 本地缓存不能被关闭, 但可以调用 clearCache()来清空本地缓存, 或者改变缓存的作用域.。
- 在mybatis3.1之后, 可以配置本地缓存的作用域.,在 mybatis.xml 中配置。
- 同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中。key:hashCode+查询的SqlId+编写的sql查询语句+参数
- 一级缓存失效的四种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
二级缓存:全局缓存,基于namespace级别的缓存,一个namespace对应一个二级缓存。
- 二级缓存 工作机制:
- 一个会话,查询一条数据,这个数据机会被放在当前会话的一级缓存中;
- 如果会话关闭,一级缓存中的数据会被保存到二级缓存;新的会话查询信息,就可以参照二级缓存。
- 不同namespace查出的数据会放在自己的缓存中(map)。
- 二级缓存
- 二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
- 二级缓存的使用
- 开启全局二级缓存的配置
- 去mapper.xml中配置使用二级缓存
- POJO实现反序列化接口
六. mybatis-spring
略
七. mybatis工作原理
获取sqlSessionFactory对象:
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
注意:【MappedStatement】:代表一个增删改查的详细信息
获取sqlSession对象
返回一个DefaultSQlSession对象,包含Executor和Configuration;
这一步会创建Executor对象;
获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
代理对象里面包含了,DefaultSqlSession(Executor)
- 总结:
- 根据配置文件(全局,sql映射)初始化出Configuration对象
- 创建一个DefaultSqlSession对象,
他里面包含Configuration以及
Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor) - DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
- MapperProxy里面有(DefaultSqlSession);
- 执行增删改查方法:
调用DefaultSqlSession的增删改查(Executor);
会创建一个StatementHandler对象。(同时也会创建出ParameterHandler和ResultSetHandler)
调用StatementHandler预编译参数以及设置参数值;。使用ParameterHandler来给sql设置参数
调用StatementHandler的增删改查方法; - ResultSetHandler封装结果
四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);