MyBatis知识概括
MyBatis介绍
MyBatis简介:
- MyBatis 是支持定制化 SQL、存储过程以及高级 映射的优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设 置参数以及获取结果集。
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录.
为什么要使用MyBatis?
- MyBatis是一个半自动化的持久化层框架。
JDBC:
①SQL夹在Java代码块里,耦合度高导致硬编码内伤
②维护不易且实际开发需求中sql是有变化,频繁修改的情况多见Hibernate和JPA:
①长难复杂SQL,对于Hibernate而言处理也不容易
②内部自动生产的SQL,不容易做特殊优化。
③基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降。- 对开发人员而言,核心sql还是需要自己优化
- sql和java编码分开,功能边界清晰,一个专注业务、 一个专注数据
MyBatis环境搭建步骤:
创建MyBatis全局配置文件:
①MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息、如数据库连接池信息等。
②指导着MyBatis进行工作。我们可以参照官方文件的配置示例。创建SQL映射文件:
①映射文件的作用就相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件。案例:
①根据全局配置文件,利用 SqlSessionFactoryBuilder创建SqlSessionFactory。
②使用SqlSessionFactory获取sqlSession对象。一个 SqlSession对象代表和数据库的一次会话。
③使用SqlSession根据方法id进行操作
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession openSession = factory.openSession();
SqlSession:
①SqlSession 的实例不是线程安全的,因此是不能被共享的。
②SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的
③SqlSession可以直接调用方法的id进行数据库操作,
④但是我们一般还是推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作
SqlSession openSession = factory.openSession();
//根据方法id进行操作
try{
Object one = openSession.selectOne("com,george.dao.EmployeeMapper.getEmpById",1);
System.out.println(one);
}finally{
openSession.close;
}
//根据映射器进行操作
try{
EmployeeMapper mapper= openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(employee );
}finally{
openSession.close;
}
Mybatis IO处理工具Resources和ClassLoaderWrapper:
- Resources类位于org.apache.ibatis.io包下,用于处理相关的IO操作,例如将文件,网络资源进行读取并转换为File,InputStream,Reader,URL等Java类。
- Resources类提供了多个getResourceAsxxx()方法作为对外暴露的方法,Resources并不提供方法实现,而是内部维护着一个静态ClassLoaderWrapper实例,
- Resources中的方法都是通过调用ClassLoaderWrapper相关方法实现。下面列出了Resources最常用的方法。
- 链接:Mybatis IO处理工具Resources和ClassLoaderWrapper详细链接
MyBatis-全局配置文件
MyBatis-全局配置文件:
- MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。
- 文档的顶层结构如下:
•configuration 配置
• properties 属性
• settings 设置
• typeAliases 类型命名
• typeHandlers 类型处理器
• objectFactory 对象工厂
• plugins 插件
• environments 环境
• environment 环境变量
• transactionManager 事务管理器
• dataSource 数据源
• databaseIdProvider 数据库厂商标识
• mappers 映射器
properties属性:
- mybatis也支持我们通过外部properties文件来配置一些属性信息。
- mybatis配置属性信息有3种方式:
①property元素中定义属性,并使用${属性名称}引用属性的值。
<1>name:属性的名称
<2>value:属性的值。
②resource引入配置文件
,mybatis支持从外部引入配置文件,可以把配置文件写在其他外部文件中,然后进行引入。
<1>properties元素有个resource属性,值为配置文件相对于classes的路径。
③url的方式引入远程配置文件
,mybatis还提供了引入远程配置文件的方式。
④属性配置文件使用建议:建议可以使用第二种方式来引入外部资源配置文件。
⑤如果3种方式如果我们都写了,mybatis会怎么走?
<1>方式1和方式2都存在的时候,方式2的配置会覆盖方式1的配置。
<2>如果方式2和方式3都存在的时候,方式3会失效
<3>mybatis会先读取方式1的配置,然后读取方式2或者方式3的配置,会将1中相同的配置给覆盖。
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="driver" value="${jdbc.driver}"/>
<properties resource="配置文件路径"/>
<properties url="远程配置文件的路径" />
settings设置:
- 这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为。
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存 的全局开关。 | true | false | TRUE |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载 。特定关联关系中可通过设置fetchType 属性来覆盖该项的开关状态。 | true | false | FALSE |
useColumnLabel | 使用列标签代替列名 。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true | false | TRUE |
defaultStatementTimeout | 设置超时时间 ,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则( camel case )映射 ,即从经典数据库列名A_COLUMN到经典Java属性名aColumn的类似映射 | true | false | FALSE |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J| LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | Not set |
typeAliases别名处理器:
- 在xml文件中有很多需要类完整的类名的地方,十分的冗长,为了减轻我们的工作量和复杂度mybatis支持我们给某个类起一个别名,然后通过别名可以访问到指定的类型。
- 别名的用法:使用别名之前需要先在mybatis中注册别名,
而注册别名有3种方式。
①使用typeAlias元素进行注册
<1>typeAliases元素中可以包含多个typeAlias子元素,每个typeAlias可以给一个类型注册别名,有2个属性需要指定:
1、type:完整的类型名称
2、alias:别名
②通过packege元素批量注册
<1>上面我们通过typeAlias元素可以注册一个别名,如果我们有很多类需要注册,需要写很多typeAlias配置。
<2>mybatis为我们提供了批量注册别名的方式,通过package元素。
<3>这个也是在typeAliases元素下面,不过这次使用的是package元素,package有个name属性,可以指定一个包名,mybatis会加载这个包以及子包中所有的类型,给这些类型都注册别名,别名名称默认会采用类名小写的方式,如UserModel的别名为usermodel
③package结合@Alias批量注册并指定别名
<1>方式2中通过package可以批量注册别名,但是容易出现多个类名相同的类。
<2>所以在package方式批量注册别名的时候,我们可以给类中添加一个@Alias注解来给这个类指定别名。
<3>当mybatis扫描类的时候,发现类上有Alias注解,会取这个注解的value作为别名,如果没有这个注解,会将类名小写作为别名,如同方式2。
<typeAliases>
<typeAlias type="完整的类型名称" alias="别名" />
</typeAliases>
<typeAliases>
<package name="需要扫描的包"/>
</typeAliases>
@Alias("user")
public class UserModel {
}
- 值得注意的是:
①别名不区分大小写
②MyBatis已经为许多常见的 Java类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。
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 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
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 |
日期类型的处理:
①日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用JSR310
规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了。
②日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器
。
③MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。
自定义类型处理器:
①我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
②步骤:
<1>实现org.apache.ibatis.type.TypeHandler接口
或者继承org.apache.ibatis.type.BaseTypeHandler
<2>指定其映射某个JDBC类型(可选操作)
<3>在mybatis全局配置文件中注册
------------------------------
需求场景:当数据库中保存'Y'/'N',而对应bean字段的值的类型为boolean,这是就需要我们自定义类型转换
器,在Mybatis执行SQL得到结果时,通过自定义类型转换器将CHAR或者VARCHAR2类型转换为boolean类型,
Java代码如下:
------------------------------
/**
* @author afei
* java中的boolean和jdbc中的char之间转换;true-Y;false-N
*/
@MappedJdbcTypes({JdbcType.VARCHAR}) //对应数据库类型
@MappedTypes({Boolean.class}) //java数据类型
public class mangoBooleanTypeHandler implements TypeHandler<Boolean> {
private static final Logger logger = Logger.getLogger(mangoBooleanTypeHandler.class) ;
/**
* 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型
* @param preparedStatement 当前的PreparedStatement对象
* @param i 当前参数的位置
* @param b 当前参数的Java对象
* @param jdbcType 当前参数的数据库类型
* @throws SQLException
*/
public void setParameter(PreparedStatement preparedStatement, int i, Boolean b, JdbcType jdbcType) throws SQLException {
if(b.equals(Boolean.TRUE)){
preparedStatement.setString(i,"Y");
}else{
preparedStatement.setString(i,"N");
}
}
/**
* 用于在Mybatis获取数据结果集时如何把数据库类型转换为对应的Java类型
* @param resultSet 当前的结果集
* @param columnName 当前的字段名称
* @return 转换后的Java对象
* @throws SQLException
*/
public Boolean getResult(ResultSet resultSet, String columnName) throws SQLException {
return tranferType(resultSet.getString(columnName)) ;
}
/**
* 用于在Mybatis通过字段位置获取字段数据时把数据库类型转换为对应的Java类型
* @param resultSet 当前的结果集
* @param i 当前字段的位置
* @return 转换后的Java对象
* @throws SQLException
*/
public Boolean getResult(ResultSet resultSet, int i) throws SQLException {
return tranferType( resultSet.getString(i) );
}
/**
* 用于Mybatis在调用存储过程后把数据库类型的数据转换为对应的Java类型
* @param callableStatement 当前的CallableStatement执行后的CallableStatement
* @param columnIndex 当前输出参数的位置
* @return
* @throws SQLException
*/
public Boolean getResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
return tranferType(callableStatement.getString(columnIndex));
}
private Boolean tranferType(String s){
if("Y".equalsIgnoreCase(s))
{
return Boolean.TRUE ;
}else{
return Boolean.FALSE;
}
}
}
plugins插件:
- 插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。
插件通过动态代理机制,可以介入四大对象的任何一个方法的执行
。后面会有专门的章节我们来介绍mybatis运行原理以及插件。
①Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
②ParameterHandler
(getParameterObject, setParameters)
③ResultSetHandler
(handleResultSets, handleOutputParameters)
④StatementHandler
(prepare, parameterize, batch, update, query)
databaseIdProvider环境:
- MyBatis 可以根据不同的数据库厂商执行不同的语句。
①Type: DB_VENDOR
<1>使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
②Property-name:数据库厂商标识。
③Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用。
④DB_VENDOR:会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短。
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同的数据库厂商起别名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<select id="getEmpsByDeptId" resultType="Emp" parameterType="Integer"
databaseId="mysql">
slelect * from tbl_emp WHERE deptId = #{id}
</select>
MyBatis匹配规则如下:
①如果没有配置databaseIdProvider标签,那么databaseId=null
②如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
③如果databaseId不为null,他只会找到配置databaseId的sql语句
④MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带databaseId 的相同语句,则后者会被舍弃。
mapper映射:
- mapper xml文件是非常重要的,我们写的sql基本上都在里面,使用mybatis开发项目的时候,和mybatis相关的大部分代码就是写sql,基本上都是和mapper xml打交道。
- 编写好的mapper xml需要让mybatis知道,我们怎么让mybatis知道呢?
可以通过mybatis全局配置文件进行引入,主要有4种方式。
①使用mapper resouce属性注册mapper xml文件
<1>目前我们所涉及到的各种例子都是采用的这种方式,使用下面的方法进行引入:
<2>注意事项:一般情况下,我们会创建一个和Mapper xml中namespace同名的Mapper接口,Mapper接口会和Mapper xml文件进行绑定,mybatis加载mapper xml的时候,会去查找namespace对应的Mapper接口,然后进行注册,我们可以通过Mapper接口的方式去访问Mapper xml中的具体操作。
<3>Mapper xml和Mapper 接口配合的方式是比较常见的做法,也是强烈建议大家使用的
②使用mapper标签url属性注册Mapper文件
<1>通过url引入网络资源或者本地磁盘资源
③使用mapper class属性注册Mapper接口
<1>引入Mapper接口,这种情况下,mybais会去加载class对应的接口,然后还会去加载和这个接口同一个目录的同名的xml文件。
<2>例如配置mapper class=“zhonghu.chat01.demo1.UserMapper”,mybatis会自动去注册UserMapper接口,还会去查找下面的文件:zhonghu.chat01.demo1.UserMapper.xml。
<3>开发项目的时候估计也会看到这种写法,Mapper接口和Mapper xml文件放在同一个包中。
④使用package元素批量注册Mapper接口
<1>package批量注册Mapper接口,上面说2种方式都是一个个注册mapper的,如果我们写了很多mapper,是否能够批量注册呢?
<2>mybatis会扫描package元素中name属性指定的包及子包中的所有接口,将其当做Mapper 接口进行注册,所以一般我们会创建一个mapper包,里面放Mapper接口和同名的Mapper xml文件。
<3>使用注意:方式3会扫描指定包中所有的接口,把这些接口作为Mapper接口进行注册,扫描到的类型只要是接口就会被注册,所以指定的包中通常我们只放Mapper接口,避免存放一些不相干的类或者接口。
<mappers>
<mapper resource="Mapper xml的路径(相对于classes的路径)"/>
</mappers>
<mappers>
<mapper class="接口的完整类名" />
</mappers>
<mappers>
<package name="需要扫描的包" />
</mappers>
- 此外对于注册接口时:
①有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下。
②没有sql映射文件,所有的sql都是利用注解写在接口上。
- 推荐:
①比较重要的,复杂的Dao接口我们来写sql映射文件。
②不重要,简单的Dao接口为了开发快速可以使用注解。
<1>链接: mapper的sql注解开发 总结:
①注册接口的方式太复杂:
<1>要么资源文件放在src/java/下需要在pom.xml去配置build
<2>要么需要在src/resources/下创建同一包名。
<3>链接:Mapper接口和mapper.xml的文件位置问题
②因此注册配置文件的方式简便一点,但需要在于spring与mybatis的整合配置文件中加上以下属性。
<property name="mapperLocations" value="classpath:mapper/*.xml"> </property>
MyBatis-Spring整合:
SSM整合中的数据源不使用Mybatis中的数据源 而是在spring配置文件中使用第三方的数据源如德鲁伊,CP30。
sqlSessionFactory标签中可以定义以下子标签:
①configLocation:配置文件位置
②dataSource:数据源
③mapperLocations:映射文件位置
④typeAliasesPackage:别名定义的包范围MapperScannerConfigurer的作用
:配置basePackage用来扫描指定路径的类注入到ioc容器。
①在springBoot中可以使用@MapperScan(“mapper文件所在包”)代替
②也可以使用@Mapper注解代替,不过要逐个在mapper接口上加上。
<1>@Mapper 是 Mybatis 的注解,和 Spring 没有关系
③使用@repository或者@Component的话则是加在mapper接口的实现类上的,而且还需要@ComponentScan注解扫描。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定mybatis全局配置文件位置 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<!--指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!--mapperLocations:所有sql映射文件所在的位置 -->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
<!--typeAliasesPackage:批量别名处理-->
<property name="typeAliasesPackage" value="com.atguigu.bean"></property>
</bean>
<!--自动的扫描所有的mapper的实现并加入到ioc容器中 -->
<bean id="configure" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!– basePackage:指定包下所有的mapper接口实现自动扫描并加入到ioc容器中 -->
<property name="basePackage" value="com.atguigu.dao"></property>
</bean>
environments环境:
- MyBatis可以配置多种环境,
比如开发、测试和生产环境需要有不同的配置。
每种环境使用一个environment标签进行配置并指定唯一标识符。
可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境。
- environment-指定具体环境:
①id:指定当前环境的唯一标识
②transactionManager
和dataSource
都必须有。 transactionManager事务管理器,其属性type:
①JDBC:
使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围。JdbcTransactionFactory
②MANAGED:
不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。ManagedTransactionFactory
③自定义:
实现TransactionFactory接口,type=全类名/别名dataSource数据源,其属性type(类型):
①UNPOOLED
:不使用连接池, UnpooledDataSourceFactory
②POOLED
:使用连接池, PooledDataSourceFactory
③JNDI
: 在EJB 或应用服务器这类容器中查找指定的数据源
④自定义
:实现DataSourceFactory接口,定义数据源的获取方式。- 总结:实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置。
<environments default="dev_mysql">
<environment id="dev_mysql">
<transactionManager type="JDBC"></transactionManager>
<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>
<environment id="dev_oracle">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.username}" />
<property name="password" value="${orcl.password}" />
</dataSource>
</environment>
</environments>
DML、DQL与事务:
- 对于DML数据操作,我们必须要记住提交事务,如果autocommit为true的话,当然就不用我们自己操心了,数据库会帮我们提交的。
- 但是在MyBatis持久层框架中,
进行DML操作时我们必须要手动开启事务,并且手动提交事务。
①因为在MyBatis持久层框架中,它们处理DML语句的时候会自动设置autocommit=false;如果DML中不进行手动提交事务,那么最后事务就会进行回滚。
- 为什么不设置autocommit=true自动提交呢?
①因为autocommit是针对于单条sql语句的自动提交。
②反而我们真实写项目的时候,用到的事务都是包含多条sql语句
,所以我们不得不自己手动显示开启事务,并且手动进行事务提交,以此来创建一个事务作用边界。 MyBatis框架中DQL不需要我们手动开启事务或者提交事务
。我们不做同样能够读取到最新数据,DQL的事务开启或者提交是框架帮我们做。
①DQL不同隔离级别查询的值会不同
,针对于MySQL,InnoDB的REPEATABLE_READ,其保证了同一个事务里,查询的结果和开启事务时并且第一次查询数据时的数据总是一样的。
②有很多程序员说,DQL不需要开启事务
,但是通过我们的最佳实践知道,就算我们不开启事务,数据库也会帮我们开启事务
。而且在一个事务中查询数据还会一直相同(REPEATABLE_READ事务隔离级别)。所以这和很多程序员的观点:”DQL不需要开启事务”是矛盾的。
MyBatis-映射文件
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义:
- cache –命名空间的二级缓存配置
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 自定义结果集映射
- parameterMap – 已废弃!老式风格的参数映射
- sql –抽取可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
insert、update、delete元素:
参数名 | 参数作用 |
---|---|
id | 命名空间中的唯一标识符 |
parameterType | 将要传入语句的参数的完全限定类名和别名,这个属性是可选的,因为Mybatis可以通过TypeHandler推断出具体传入语句的参数类型,默认值为unset 。 |
flushCache | 将其设置为true,任何时候只要语句被调用,都回导致本地缓存和二级缓存都会被清空,默认值:true(对于插入、更新和删除语句) |
timeout | 这个设置实在跑出异常之前,驱动程序等待数据库返回请求结果的秒数 ,默认值为unset(依赖驱动) |
statementType | STATEMENT :直接操作sql,不进行预编译,获取数据$。PREPARED :预处理,参数,进行预编译,获取数据#。CALLABLE :CALLABLE:执行存储过程。选择其中一个这会让Mybatis分别使用Statement、PreparedStatement 或CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅对insert和update有用)这会令Mybatis使用JDBC的getGeneratedKeys方法取出由数据库内部生成的主键 (比如:像Mysql和SQL Server这样的关系型数据库管理系统的自动递增字段),默认值false |
keyProperty | (仅对insert和update有用)唯一标记一个属性,Mybatis会通过getGeneratedKeys的返回值或者通过insert语句的selectkey子元素设置它的键值,默认unset。 |
keyColumn | (仅对insert和update有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置,如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
databaseId | 如果配置了databaseProvider,Mybatis会加载所有的不带databaseId或匹配当前databaseId的语句 ,如果带或者不带的语句都有,则不带的会被忽略。 |
delete,update,insert返回值问题:
- defaultExecutorType有三个执行器SIMPLE、REUSE和BATCH。
①SIMPLE 执行器执行其它语句
②REUSE 可以重复使用prepared statements 语句
③BATCH 可以重复执行语句和批量更新 - 其中BATCH可以批量更新操作缓存SQL以提高性能,但是有个缺陷就是无法获取update、delete返回的行数。
defaultExecutorType的默认执行器是SIMPLE。
- 总结:
①为BATCH 时执行成功返回-1,执行失败返回0
②为SIMPLE 时执行成功返回影响条数,执行失败返回0
③执行错误抛出异常。
主键生成方式:
- 若数据库支持自动生成主键的字段(比如 MySQL 和 SQLServer),则可以设置useGeneratedKeys=”true”,然后再把keyProperty 设置到目标属性上。
- 而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素: selectKey元素将会首先运行,id 会被设置,然后插入语句会被调用。
selectKey:
sql:
- sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用。
- include来引用已经抽取的sql。
- include还可以自定义一些property,sql标签内部就能使用自定义的属性。
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
- 这个 SQL 片段可以在其它语句中使用,例如:
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
- 也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
参数(Parameters)传递:
- 单个参数
可以接受基本类型,对象类型,集合类型的值。这种情况MyBatis可直接使用这个参数,不需要经过任何处理。 - 多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。map的key:arg0、arg1或者是param1、param2等,map的value:实际调用方法传入的参数值。(通常我们习惯用参数的name作为key,因此需要使用命名参数) - 命名参数(可传多个参数)
为参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字 - POJO(可传多个参数)
当这些参数属于我们业务POJO时,我们直接传递POJO - Map(可传多个参数)
我们也可以封装多个参数为map,直接传递 - DTO (可传多个参数)
当参数过多,切该Map需要经常使用时,可以将Map封装成一个DTO对象。
参数处理:
- 参数位置支持的属性:
①javaType:和 MyBatis 的其它部分一样,几乎总是可以根据参数对象的类型确定 javaType,除非该对象是一个 HashMap。这个时候,你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用。
②jdbcType: JDBC 要求,如果一个列允许使用 null 值,并且会使用值为 null 的参数,就必须要指定 JDBC 类型(jdbcType)
③numericScale:对于数值类型,还可以设置 numericScale 指定小数点后保留的位数。
④typeHandler:要更进一步地自定义类型处理方式,可以指定一个特殊的类型处理器类(或别名)
⑤mode
⑥resultMap
⑦jdbcTypeName
⑧expression
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2,typeHandler=MyTypeHandler}
- #{key}:获取参数的值,预编译到SQL中。安全。
- ${key}:获取参数的值,拼接到SQL中。有SQL注入问题。ORDER BY ${name}
Select元素来定义查询操作:
- Id:唯一标识符。 用来引用这条语句,需要和接口的方法名一致
- parameterType:参数类型。可以不传,MyBatis会根据TypeHandler自动推断
- resultType:返回值类型。 别名或者全类名,如果返回的是集合,定义集合中元 素的类型。不能和resultMap同时使用
自动映射:
-
有三种自动映射等级:
①NONE - 禁用自动映射。仅对手动映射的属性进行映射。
②PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
③FULL - 自动映射所有属性。 -
全局setting设置
①autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
②如果autoMappingBehavior设置为null则会取消自动映射
③数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMNaColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。 -
自定义resultMap,实现高级结果集映射。
resultMap:
- constructor - 类在实例化时, 用来注入结果到构造方法中
①idArg - ID 参数; 标记结果作为 ID 可以帮助提高整体效能
②arg - 注入到构造方法的一个普通结果 - id – 一个 ID 结果; 标记结果作为 ID 可以帮助提高整体效能
- result – 注入到字段或 JavaBean 属性的普通结果
- association – 一个复杂的类型关联;许多结果将包成这种类型
①嵌入结果映射 – 结果映射自身的关联,或者参考一个 - collection– 复杂类型的集
①嵌入结果映射 – 结果映射自身的集,或者参考一个 - discriminator – 使用结果值来决定使用哪个结果映射
①case – 基于某些值的结果映射
②嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。
id & result:
- id 和 result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。
association:
- 复杂对象映射
- POJO中的属性可能会是一个对象
- 我们可以使用联合查询,并以级联属性的方式封装对象。
- 使用association标签定义对象的封装规则
association-嵌套结果集:
<resultMap type="com.bean.Lock" id="myLock">
<id column="id" property="id"/>
<result column="lock_name" property="lockName"/>
<association property="key" javaType="com.bean.Key">
<id column="key_id" property="id"/>
<result column="key_name" property="keyName"/>
</association>
</resultMap>
association-分段查询:
<resultMap type="com.bean.Lock" id="myLock">
<id column="id" property="id"/>
<result column="lock_name" property="lockName"/>
<association property="key"
select="com.dao.KeyMapper.getKeyId"
column="key_id">
</association>
</resultMap>
- select:调用目标的方法查询当前属性的值
- column:将指定列的值传入目标方法
association-分段查询&延迟加载:
- 开启延迟加载和属性按需加载
<settings>
<setting name="LazyLoadingEnabled" value="true">
<setting name="aggressiveLazyLoading" value="false">
</settings>
Collection-集合类型&嵌套结果集:
<resultMap type="com.bean.Department" id="myDeptSetp">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="emps" ofType="com.bean.Employee"
columnPrefix="e_">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="emial" property="emial"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
Collection-分步查询:
<resultMap type="com.bean.Department" id="myDeptSetp">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="emps"
select="com.dao.EmployeeMapper.getEmpsByDeptId"
column="id">
</collection>
</resultMap>
Collection-分步查询&延迟加载:
- 开启延迟加载和属性按需加载
<settings>
<setting name="LazyLoadingEnabled" value="true">
<setting name="aggressiveLazyLoading" value="false">
</settings>
扩展-多列值封装map传递:
- 分步查询的时候通过column指定,将对应的列的数据传递过去,我们有时需要传递多列数据。使用{key1=column1,key2=column2…}的形式
<resultMap type="com.bean.Department" id="myDeptSetp">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
<collection property="emps"
select="com.dao.EmployeeMapper.getEmpsByDeptId"
column="{deptId=id}">
</collection>
</resultMap>
- association或者collection标签的fetchType=eager/lazy可以覆盖全局的延迟加载策略,指定立即加载(eager)或者延迟加载(lazy)
总结:
- association是用于一对一和多对一,而collection是用于一对多的关系。
MyBatis-动态SQL
简述:
- 动态 SQL是MyBatis强大特性之一。极大的简化我们拼装 SQL的操作。
- 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处 理器相似。
- MyBatis 采用功能强大的基于 OGNL的表达式来简化操作。
①if
②choose (when, otherwise)
③trim (where, set)
④foreach - 其中set是update语句中的set。
mybatis=<>的写法:
- 第一种写法:
①原符号 < <= > >= & ’ "
②替换符号 < <= > >= & ' "
③例如:sql如下:
create_date_time >= #{startTime} and create_date_time <= #{endTime} - 第二种写法:
①大于等于 <![CDATA[ >= ]]>
②小于等于 <![CDATA[ <= ]]>
③例如:sql如下:
create_date_time <![CDATA[ >= ]]> #{startTime} and create_date_time <![CDATA[ <= ]]> #{endTime}
if:
- 使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
choose、when、otherwise:
- 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像Java 中的 switch 语句。
- 还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author”查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机Blog,还不如返回一些由管理员精选的 Blog)。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim、where、set:
- 前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
- 如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
- 这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
- 这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
- MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
- 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
- prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
- 用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
- 这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
- 来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
- 注意,我们覆盖了后缀值设置,并且自定义了前缀值。
foreach:
- 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
- foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
- 提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者Map.Entry 对象的集合)时,index 是键,item 是值。
script:
- 要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
bind:
- bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
MySQL下批量保存:
- MySQL下批量保存可以foreach遍历。
- mysql支持values(),(),()语法。
- 这种方式需要数据库连接属性allowMultiQueries=true;
- 这种分号分隔多个sql可以用于其他的批量操作(删除,修改)。
allowMultiQueries:
- 允许使用“;”在一个语句中分隔多个查询(真/假),默认为“假”,
- 并且不影响addBatch()和executeBatch()方法,addBatch()和executeBatch()方法依赖于rewriteBatchStatements。
bind:
bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:
<select id="getEmpByLastNameLike" resultType="com.bean.Employee">
<bind name="myLastName" value="'%'+lastName+'%'"/>
select * from employee where last_name like #{myLastName}
</select>
两个内置参数:
- mybatis默认还有两个内置参数:
- _parameter:代表整个参数
①单个参数:_parameter就是这个参数
②多个参数:参数会被封装为一个map;_parameter就是代表这个map - _databaseId:如果配置了databaseIdProvider标签。
①_databaseId就是代表当前数据库的别名
OGNL表达式
OGNL简介:
- OGNL(Object-Graph Navigation Language对象图导航语言),它是一门表达式语言,除了用来设置和获取Java对象的属性之外,
- 另外提供诸如集合的投影(只取部分字段(列))和 过滤(只取部分记录(行))以及lambda表达式等。
Ognl中常用到的两个类:
- ognl.Ognl类:这个类主要用来解析和解释执行Ognl表达式。
- ognl.OgnlContext类:这个类为Ognl表达式提供了一个执行环境,这个类实现了Map接口,所以允许通过put(key,obj)方法向OgnlContext环境中方式各种类型的对象,需要注意的是在OgnlContext中对象分为两种,第一种是叫做root对象(根对象),在整个OgnlContext中有且最多只能有一个根对象,可以通过调用OgnlContext.setRoot(obj)设置为根对象,另外一种就是OgnlContext中的普通对象,这种个数类型不受限制,那么既然分为两种方式,肯定在获取对象属性的方式上是有所不同的
一、获取普通对象的属性值方式;
对于普通对象,只能通过"#键值.属性名称"的方式去获取属性值,需要注意的是键值指的是放置到上下文中key的值,另外在属性值类型中要提供getName方法;
二、获取根对象的属性值的方式,有两种:
①跟上述方式一样。
②直接写属性名称就可以,注意:这个时候就不要加“#”,
因为如果Ognl在解析表达式的时候发现表达式开头带有"#",会去普通对象中,去寻找,如果没有"#",则会默认去根对象中去寻找,由于根对象只有一个,所以只需要属性名字。
//将数据放入Ognl上下文环境中
context.put(key, value);
// 将数据设置为根对象
context.setRoot(key);
// 构建Ognl表达式的树状表示,用来获取
Object expression = Ognl.parseExpression(Ognl);
// 通过上下文环境对象,根对象来解析树状表达式,返回结果
Object object= Ognl.getValue(expression, context, context.getRoot());
OGNL中重要的3个符号:#、%、$:
- #符号
#符号的用途一般有三种:
①访问非根对象属性。
②用于过滤和投影(projecting)集合。
③用来构造Map。 - %符号
%符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值。这个类似js中的eval()。
eval()方法说明:该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。因此请不要为 eval() 函数传递 String 对象来作为参数。 - $符号
$符号主要有两个方面的用途:
①在国际化资源文件中,引用OGNL表达式。
②在Struts 2框架的配置文件中引用OGNL表达式。
处理数组、集合和Map对象:
- 数组
" #数组名称[索引]" - 集合
" #集合名称[索引]" - Map
" #Map名称[键值]"
集合的过滤:
指的是将原集合中不符合条件的对象过滤掉,然后将满足条件的对象,构建一个新的集合对象返回。
基础语法:"#集合名称.{?|^|$ 运算表达式}"
"#persons.{?#this.name=='pla1'}"
集合的投影:
指的是将原集合中所有对象的某个属性抽取出来,单独构成一个新的集合对象返回。
基础语法 :"#集合名称.{运算表达式}"
"#persons.{name}"
构造Map:
"#{‘foo1’:’bar1’, ‘foo2’:’bar2’}"
总结:
- 上述中的{}表示一个集合
- #this代表当前元素
- ?表示所有,^表示第一个,$ 表示最后一个。
表达式支持如下运算符:
关系运算,逻辑运算,算数运算,三元运算。
Mybatis使用OGNL
MyBatis中可以使用OGNL的地方有两处:
- 动态SQL表达式中
- ${param}参数中
表达式e1,e2:
- e1 or e2
- e1 and e2
- e1 == e2, e1 eq e2
- e1 != e2, e1 neq e2
- e1 < e2, e1 lt e2
- e1 <= e2, e1 lte e2
- e1 > e2, e1 gt e2
- e1 >= e2, e1 gte e2
- e1 in e2
- e1 not in e2
- ! e, not e,e instanceof class
- e.method(args)调用对象方法
- e.property调用对象属性
- e1[ e2 ] 按索引取值,list,数组和map
- @class@method(args) 调用静态方法
- @class@field 调用静态常量
详细资料:MyBatis中的OGNL教程
MyBatis-缓存机制
简介:
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。
- 缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
一级缓存:
一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession(会话)。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
- 本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域。
- 在mybatis3.1之后, 可以配置本地缓存的作用域:在 mybatis.xml 中配置。
- 一级缓存演示&失效情况:
①同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
②一级缓存失效的四种情况:
<1>不同的SqlSession对应不同的一级缓存
<2>同一个SqlSession但是查询条件不同
<3>同一个SqlSession两次查询期间执行了任何一次增删改操作
<4>同一个SqlSession两次查询期间手动清空了缓存
二级缓存:
二级缓存(second level cache)是全局作用域缓存,二级缓存是基于namespaces级别的缓存,一个namespaces 对应一个二级缓存。不同的namespaces查询的数据会放在自己对应的缓存中(Map中)。
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
- 使用步骤:
①全局配置文件中开启二级缓存: <setting name=“cacheEnabled” value=“true”/>
②需要使用二级缓存的映射文件处使用cache配置缓存<cache>
③注意:POJO需要实现Serializable接口
缓存相关属性:
- eviction=“FIFO”:缓存回收策略:
①LRU – 最近最少使用的:移除最长时间不被使用的对象。
②FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
③SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
④WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
⑤默认的是 LRU。 - flushInterval:刷新间隔,单位毫秒
①默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 - size:引用数目,正整数
①代表缓存最多可以存储多少个对象,太大容易导致内存溢出 - readOnly:只读,true/false
①true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
②false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
缓存有关设置:
- 全局setting的cacheEnable:配置二级缓存的开关。一级缓存一直是打开的。
- select标签的useCache属性:配置这个select是否使用二级缓存。一级缓存一直是使用的
- sql标签的flushCache属性:增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
- sqlSession.clearCache():只是用来清除一级缓存。
- 当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有select中的缓存将被clear。
第三方缓存整合:
- EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
- MyBatis定义了Cache接口方便我们进行自定义扩展。
- 步骤:
①导入ehcache包,以及整合包,日志包ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar
slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
②编写ehcache.xml配置文件
③配置cache标签<cache type=“org.mybatis.caches.ehcache.EhcacheCache”></cache> - 参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存
缓存流程:
MyBatis-逆向工程
MyBatis-逆向工程(MyBatis Generator):
- 一般来说我们是根据数据表正向地编写映射文件、接口、以及bean类。
- MyBatis-逆向工程简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表逆向分析并生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。
- 但是表连接、存储过程等这些复杂sql的定义需要我们手工编写。
- 链接:
①官方文档地址
②官方工程地址
使用步骤:
- 编写MBG的配置文件(重要几处配置):
①jdbcConnection配置数据库连接信息
②javaModelGenerator配置javaBean的生成策略
③sqlMapGenerator 配置sql映射文件生成策略
④javaClientGenerator配置Mapper接口的生成策略
⑤table 配置要逆向解析的数据表
<1>tableName:表名
<2>domainObjectName:对应的javaBean名 - 运行代码生成器生成代码
- 注意:
①Context标签
<1>targetRuntime=“MyBatis3“可以生成带条件的增删改查
<2>targetRuntime=“MyBatis3Simple“可以生成基本的增删改查
<3>如果再次生成,建议将之前生成的数据删除,避免xml向后追加内容出现的问题。 - MBG配置文件:
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
//数据库连接信息配置
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/bookstore0629"
userId="root" password="123456">
</jdbcConnection>
//javaBean的生成策略
<javaModelGenerator targetPackage="com.george.bean" targetProject=".\src">
//是否让schema作为包的后缀
<property name="enableSubPackages" value="true" />
//是否清理从数据库返回的值被前后的空格
<property name="trimStrings" value="true" />
</javaModelGenerator>
//sql映射文件的生成策略
<sqlMapGenerator targetPackage="mybatis.mapper" targetProject=".\conf">
//是否让schema作为包的后缀
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
//dao接口java文件的生成策略
<javaClientGenerator type="XMLMAPPER" targetPackage="com.george.dao"
targetProject=".\src">
//是否让schema作为包的后缀
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
//数据表与javaBean的映射
<table tableName="books" domainObjectName="Book"></table>
</context>
</generatorConfiguration>
- 生成器代码:
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//读取mbg配置文件
File configFile = new File("mbg.xml");
//实例化配置文件解析器
ConfigurationParser cp = new ConfigurationParser(warnings);
//设置解析的配置文件
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
//实例化mybatis生成器
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
//生成
myBatisGenerator.generate(null);
}
mapper映射接口方法使用:
- XXX 和 XXXSelective的区别:
①使用逆向工程生成的代码做一个添加时通常都会给出两个答案,
<1>如想要增加一条数据会让你选择insert或者insertSelective
<2>如想要修改一条数据会让你选择update或者updateSelective
②两者的区别在于:
<1>如果选择XXX那么所有的字段都会添加一遍即使没有值。
<2>但是如果使用XXXSelective就会只给有值的字段赋值(会对传进来的值做非空判断)。
- 关于example类详解:
①example类是QBC(Query By Criteria) 查询方式里的一个类。
②链接:Hibernate的几种查询方式-HQL,QBC,QBE
③mybatis-generator会为每个字段产生Criterion
,为底层的mapper.xml创建动态sql。如果表的字段比较多,产生的example类会十分庞大。理论上通过example类可以构造你想到的任何筛选条件。
作用:升序还是降序,参数格式:字段+空格+asc(desc)
protected String orderByClause;
作用:去除重复,true是选择不重复记录,false,反之
protected boolean distinct;
自定义查询条件,Criteria的集合,集合中对象是由or连接
protected List<Criteria> oredCriteria;
内部类Criteria包含一个Cretiron的集合,每一个Criteria对象内包含的Cretiron之间是由AND连接的
public static class Criteria extends GeneratedCriteria {
protected Criteria() {super();}
}
是mybatis中逆向工程中的代码模型
protected abstract static class GeneratedCriteria {......}
是最基本,最底层的Where条件,用于字段级的筛选
public static class Criterion {......}
example类方法大全:
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public String getOrderByClause() {
return orderByClause;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public boolean isDistinct() {
return distinct;
}
public List<Criteria> getOredCriteria() {
return oredCriteria;
}
public void or(Criteria criteria) {
oredCriteria.add(criteria);
}
public Criteria or() {
Criteria criteria = createCriteriaInternal();
oredCriteria.add(criteria);
return criteria;
}
public Criteria createCriteria() {
Criteria criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
oredCriteria.add(criteria);
}
return criteria;
}
protected Criteria createCriteriaInternal() {
Criteria criteria = new Criteria();
return criteria;
}
public void clear() {
oredCriteria.clear();
orderByClause = null;
distinct = false;
}
MyBatis-工作原理
MyBatis-工作流程:
- 获取sqlSessionFactory对象:
①解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
②注意:
<1>MappedStatement:代表一个增删改查的详细信息。
<2>在MyBatis启动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象 - 获取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);
MyBatis-插件开发
MyBatis-插件开发:
- MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
- 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
①Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
②ParameterHandler
(getParameterObject, setParameters)
③ResultSetHandler
(handleResultSets, handleOutputParameters)
④StatementHandler
(prepare, parameterize, batch, update, query) - 插件开发步骤:
①编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名。
②在全局配置文件中注册插件。
@Intercepts({@Signature(type=StatementHandler.class,method="prepaer",
args={Connection.class})
})
public class MyFirstPlugin implements Interceptor{
}
<plugins>
<plugin interceptor="com.george.plugin.MyFirstPlugin ">
<property name="username" value="tomcat">
</plugin>
</plugins>
- Interceptor接口:
①Intercept:
拦截目标方法执行
②plugin:
生成动态代理对象,可以使用MyBatis提 供的Plugin类的wrap方法
③setProperties:
注入插件配置时设置的属性
插件原理:
- 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
- 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
- 目标方法执行时依次从外到内执行插件的intercept方法。
- 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
- 常用代码:
从代理链中分离真实被代理对象
//1、分离代理对象。由于会形成多次代理,所以需要通过一个
while 循环分离出最终被代理对象,从而方便提取信息
MetaObject metaObject = SystemMetaObject.forObject(target);
while (metaObject.hasGetter("h")) {
Object h = metaObject.getValue("h");
metaObject = SystemMetaObject.forObject(h);
}
//2、获取到代理对象中包含的被代理的真实对象
Object obj = metaObject.getValue("target");
//3、获取被代理对象的MetaObject方便进行信息提取
MetaObject forObject = SystemMetaObject.forObject(obj);
MyBatis实用场景
PageHelper插件进行分页:
- PageHelper是MyBatis中非常方便的第三方分页插件。
使用步骤:
①导入相关包pagehelper-x.x.x.jar 和 jsqlparser-0.9.5.jar。
②在MyBatis全局配置文件中配置分页插件。
③使用PageHelper提供的方法进行分页
④可以使用更强大的PageInfo封装返回结果- 详细链接:
①分页原理
②PageHelper探索
③浅析pagehelper分页原理 - PageHelper做的是什么呢?它封装了分页的后台部分,说得更简单点,就是你不需要每个POJO类的增删改查里都包括那两个方法了,它帮你做了。你只需要有一个selectAll的方法,它会根据你使用的数据库来将你selectAll的sql改装成一个分页查询的sql,并顺带生成一个查询总数的sql。
- PageHelper不仅帮你分页查询了数据,在返回时直接返回Page对象,里更是封装了许多分页信息。
Page<Object> page= PageHelper.startPage(1, 10);
Example example = new Example(Employee.class);
example.createCriteria().andEqualTo("employeeSex", "男");
List<Employee> list = employeeTKMapper.selectByExample(example);
PageInfo<Employee> pageinfo=new PageInfo<>(list);
- 先PageHelper.startPage(1, 10)开始分页,再selectList查询数据库的时候会自动加上limit1,10,最后封装成PageInfo的时候会自动带上页码、页大小、总数等。
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
/**
* 设置 Page 参数
*
* @param page
*/
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
- 这是setLocalPage()方法,LOCAL_PAGE是当前线程,通常存储数据为了在同一个线程
(ThreadLocal)
中都可以访问到,因此startPage()方法必须在select查询前调用
,这样才可以在同一线程中获取数据并对其分页。 - 总体来讲就是进入了doProcessPage方法,通过反射机制,首先查询出数据总数量,然后进行分页SQL的拼装。
然后将分页信息通过page对象返回
,当然也可以使用PageInfo对象将分页信息和查询的分页数据封装在一起。
PageInof属性解析:
private static final long serialVersionUID = 1L;
private int pageNum; //当前页的页码
private int pageSize; //每页的数量
private int size; //当前页的数量
//可以在页面中"显示startRow到endRow 共size条数据"
private int startRow; //当前页面第一个元素在数据库中的行号
private int endRow;//当前页面最后一个元素在数据库中的行号
private long total; //总记录数
private int pages; //总页数
private List<T> list; //结果集
private int prePage; //上一页
private int nextPage; //下一页
private boolean isFirstPage; //是否为第一页
private boolean isLastPage; //是否为最后一页
private boolean hasPreviousPage; //是否有前一页
private boolean hasNextPage; //是否有下一页
private int navigatePages; //导航页码数
private int[] navigatepageNums; //所有导航页号
private int navigateFirstPage; //导航条上的第一页
private int navigateLastPage; //导航条上的最后一页
- 其中int[] navigatepageNums一般为当前页码的
前一半
和后一半
。但当当前页码为最后一页
或第一页
时只能查出其前面
或后面
的所有页码
。
批量操作:
- 默认的 openSession() 方法没有参数,它会创建有如下特性的:
①会开启一个事务(也就是不自动提交
)
②连接对象会从由活动环境配置的数据源实例得到。
③事务隔离级别将会使用驱动或数据源的默认设置。
④预处理语句不会被复用,也不会批量处理更新。 - openSession 方法的 ExecutorType 类型的参数,枚举类型:
①ExecutorType.SIMPLE:
这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句。
②ExecutorType.REUSE:
这个执行器类型会复用预处理语句。
③ExecutorType.BATCH:
这个执行器会批量执行所有更新语句 - 批量操作我们是使用MyBatis提供的BatchExecutor进行的,它的底层就是通过jdbc攒sql的方式进行的。我们可以让他攒够一定数量后发给数据库一次。
public void test01() {
SqlSession openSession = build.openSession(ExecutorType.BATCH);
UserDao mapper = openSession.getMapper(UserDao.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
String name = UUID.randomUUID().toString().substring(0, 5);
mapper.addUser(new User(null, name, 13));
}
openSession.commit();
openSession.close();
long end = System.currentTimeMillis();
System.out.println("耗时时间:"+(end-start));
}
与Spring整合中
,我们推荐,额外的配置一个可以专 门用来执行批量操作的sqlSession
。
①需要用到批量操作的时候,我们可以注入配置的这个批量SqlSession。通过他获取到mapper映射器进行操作。
②注意:
<1>批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
<2>如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constryctor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean">
</constryctor-arg>
<constryctor-arg name="executorType" value="BATCH"></constryctor-arg>
</bean>
- mysql连接地址字段:
①rewriteBatchStatements官方文档解释:
开启批处理:addBatch()和executeBatch()。
②allowMultiQueries的官方文档解释:
允许使用“;”
在一个语句中分隔多个查询(真/假),默认为“假”,并且不影响addBatch()和executeBatch()方法,后者依赖于rewriteBatchStatements,默认值为false。
③总结来说批处理和允许使用“;”
在一个语句中分隔多个查询没区别,只不过后者需要手工操作,而前者在mysql-client底层自动操作。
存储过程:
- 实际开发中,我们通常也会写一些存储过程, MyBatis也支持对存储过程的调用
- 一个最简单的存储过程:
delimiter $$
create procedure test()
begin
select 'hello';
end $$
delimiter ;
- 存储过程的调用:
①select标签中statementType=“CALLABLE”
②标签体中调用语法:{call procedure_name(#{param1_info},#{param2_info})}
- 存储过程-游标处理:
①MyBatis对存储过程的游标提供了一个JdbcType=CURSOR的支持,可以智能的把游标读取到的数据,映射到我们声明的结果集中
自定义TypeHandler处理枚举:
- 我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候自定义参数封装策略。
- 步骤:
①实现TypeHandler接口或者继承BaseTypeHandler
②使用@MappedTypes定义处理的java类型,使用@MappedJdbcTypes定义jdbcType类型。
③在自定义结果集标签或者参数处理的时候声明使用自定义TypeHandler进行处理或者在全局配置TypeHandler要处理的javaType。
//全局配置
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.george.bean.DeptStatus">
</typeHandlers>
//测试参数位置设置自定义TypeHandler
<insert id="addDept">
insert into department(dept_name,status) values(#{deptName},
#{status,typeHandler=com.george.type.MyEnumTypeHandler})
</insert>