Mybatis简介:
- Mybatis过程:
Mybatis入门案例:
案例实现:
- 导入依赖(Mybatis和日志)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
使用日志需要配置log4j.properties
######################## 将等级为DEBUG的日志信息输出到consoleh和file两个目的地, console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
########################控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
#在控制台输出
log4j.appender.console.Target = System.out
#在DEBUG级别输出
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#日志格式
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
######################日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 在数据库中创建表并创建实体类(字段名需一致,不一致则需要取别名操作)
```java package bean;
public class Book { private String userId; private String username; private String ustatus;
public Book(String userId, String username, String ustatus) {
this.userId = userId;
this.username = username;
this.ustatus = ustatus;
}
public Book() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUstatus() {
return ustatus;
}
public void setUstatus(String ustatus) {
this.ustatus = ustatus;
}
@Override
public String toString() {
return "Book{" +
"userId='" + userId + '\'' +
", username='" + username + '\'' +
", ustatus='" + ustatus + '\'' +
'}';
}
}
-
配置Mybatis全局配置文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user"/>
<property name="username" value="root"/>
<property name="password" value="LJLljl20020728.+"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="BookMapper.xml"/>
</mappers>
</configuration>
- 配置sql映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<select id="selectBook" resultType="bean.Book">
select * from book where userId = #{id}
</select>
</mapper>
- 编写测试类 ```java package mybatistest;
import bean.Book; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test;
import java.io.IOException; import java.io.InputStream;
public class test { /**
* 1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
* 2.sql映射文件:配置每一个sql,以及sql的封装规则等
* 3.将sql映射文件注册在全局配置文件中
* 4.写代码
* (1) 根据全局配置文件得到SqlSessionFactory
* (2) 使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的依次绘画,用完关闭
* (3) 使用sql的唯一标志老高斯Mybatis执行那个sql,而sql都是保存在映射文件中的 BookMapper.xml
* @throws IOException
*/
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne中有2个参数:
* 第一个参数为sql唯一标识
* 第二个为执行sql要使用的参数
*/
try {
Book selectBook = sqlSession.selectOne("selectBook", 1);
System.out.println(selectBook);
} finally {
sqlSession.close();
}
}
}
<a name="fd89c2ec"></a>
## 面向接口编程:
-
创建映射接口
```java
package mybatistest;
import bean.Book;
public interface BookMapper {
public Book getBookOne(String userId);
}
- 修改命名空间为接口的全类名,和映射接口对应
<mapper namespace="mybatistest.BookMapper">
- id为实现接口的方法
<select id="getBookOne" resultType="bean.Book">
select * from book where userId = #{id}
</select>
测试类:
package mybatistest;
import bean.Book;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class test {
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.getBookOne("1");
System.out.println(book);
} finally {
sqlSession.close();
}
}
}
总结:
接口式编程
原生 -> Dao -> DaoImpl
mybatis -> Mapper ->xxMapper.xmlSqlSession代表和数据库一次会话:用完必须关闭
SqlSession和connection一样都是非线程安全。每次使用都应该去获取新的对象。
mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。(将接口和xml进行绑定)
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
两个重要的配置文件:
- mybatis的全局变量配置文件:包含数据库连接池信息,事务管理器操作等…系统运行环境信息。
- sql映射文件:保存了每一个sql语句的映射信息,将sql去出来。
全局配置文件:
- 全局配置文件configuration标签的配置顺序:
ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?,
objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?)
properties—引入外部配置文件:
- jdbc.properties 编写连接池信息
pro.driverClass=com.mysql.jdbc.Driver
pro.url=jdbc:mysql://localhost:3306/user
pro.username=root
pro.password=LJLljl20020728.+
- 引入外部配置文件,并替换其值
<configuration>
<properties resource="jdbc.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${pro.driverClass}"></property>
<property name="url" value="${pro.url}"></property>
<property name="username" value="${pro.username}"></property>
<property name="password" value="${pro.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="BookMapper.xml"/>
</mappers>
</configuration>
setting—运行时行为设置:
settings包含很多重要设置项
setting用来设置每一个设置项
- name:设置项名
- value:设置项取值
typeAliases—别名
typeAlias:为某个java类型取别名
- type:指定要起别名的类型全类名;默认别名就是类名小写
alias:指定新的别名
<typeAliases>
<typeAlias type="bean.Book" alias="book"></typeAlias>
</typeAliases>
在xxxmapper.xml中可以直接使用
<select id="getBookOne" resultType="book">
package:为某个包下的所有批量取别名
- name:指定包名(为当前包以及下面所有后代包的每一个类都起一个默认别名(类名小写))
<typeAliases>
<!-- <typeAlias type="bean.Book" alias="book"></typeAlias>-->
<package name="bean"/>
</typeAliases>
- name:指定包名(为当前包以及下面所有后代包的每一个类都起一个默认别名(类名小写))
- 批量取别名的情况下,使用@Alias注解为某个类型指定新的别名
@Alias(value = "book")
public class Book
plugings—插件
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
environments—环境配置
MyBatis 可以配置成适应多种环境,default指定使用某种环境(将default值设为那个环境的id值),可以达到快速切换。例如,开发、测试和生产环境需要有不同的配置。尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
默认使用的环境 ID(比如:default=”development”)。
每个 environment 元素定义的环境 ID(比如:id=”development”)。
事务管理器的配置(比如:type=”JDBC”)。
数据源的配置(比如:type=”POOLED”)。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${pro.driverClass}"></property>
<property name="url" value="${pro.url}"></property>
<property name="username" value="${pro.username}"></property>
<property name="password" value="${pro.password}"></property>
</dataSource>
</environment>
<environment id="development">
...
</environment>
</environments>
事务管理器(transactionManager):在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”)type为指定全类名
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
- 自定义事务管理器: TransactionFactory 接口实现类的全限定名或类型别名代替它们。在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类。使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。
数据源(dataSource):元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
UNPOOLED-这个数据源的实现会每次请求时打开和关闭连接。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
自定义数据源:你可以通过实现接口
org.apache.ibatis.datasource.DataSourceFactory
来使用第三方数据源实现:public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
以C3P0为例:
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
}
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>
databaseIdProvider—数据库厂商标识
- MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的
databaseId
属性。 MyBatis 会加载带有匹配当前数据库databaseId
属性和所有不带databaseId
属性的语句。 如果同时找到带有databaseId
和不带databaseId
的相同语句,则后者会被舍弃。<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<select id="getBookOne" resultType="book" databaseId="db2">
select * from book where userId = #{id}
</select>
<select id="getBookOne" resultType="book" databaseId="sqlserver">
select * from book where userId = #{id}
</select>
切换环境则可以对不同的数据库执行标识的操作。
Mappers—映射器
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括
file:///
形式的 URL),或类名和包名等。<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
class:引用(注册)接口
有sql映射文件,映射名必须和接口一致,并且放在同一目录下
没有sql映射文件,所有的sql都是利用注解紫萼在接口上
推荐:比较重要的,复杂的Dao接口我们来写SQL映射文件,不重要,简单的Dao接口为了快速开发可以使用注解
接口方法中写需要操作对应的注释public interface BookMapper {
@Select("select * from book where userId = #{id}")
public Book getBookOne(String userId);
}
配置相应的mapper
<mappers>
<mapper class="mybatistest.BookMapper"></mapper>
</mappers>
- 批量注册:(情况和class相似,分两种情况)
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
XML映射文件:
增删改操作:
mybatis允许增删改直接定义Integer,Long,Boolean类型返回值,mybatis会自动封装结果
需要手动提交 sqlSessionFactory.openSession() 不需要手动提交 sqlSessionFactory.openSession(true)
| 属性 | 描述 | | :—- | :—- | |
id
| 在命名空间中唯一的标识符,可以被用来引用这条语句。 | |parameterType
| 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 | |parameterMap
| 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 | |flushCache
| 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 | |timeout
| 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 | |statementType
| 可选 STATEMENT,PREPARED 或 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
| 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
实例:
- 映射接口
public interface BookMapper {
// @Select("select * from book where userId = #{id}")
public Book getBookOne(String userId);
public void updateBook(Book book);
public void deleteBook(String userId);
public void addBook(Book book);
}
- 映射接口
- 映射文件
<mapper namespace="mybatistest.BookMapper">
<!--
namespace:名称空间
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<select id="getBookOne" resultType="book">
select * from book where userId = #{id}
</select>
<update id="updateBook" >
update book set userId=#{userId},username=#{username},ustatus=#{ustatus} where userId=#{userId}
</update>
<insert id="addBook">
insert into book (userId,username,ustatus) values (#{userId},#{username},#{ustatus})
</insert>
<delete id="deleteBook">
delete from book where userId=#{id}
</delete>
</mapper>
- 测试类
@Test
public void test1() throws IOException {
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
//添加
mapper.addBook(new Book("3","C#","5"));
//修改
mapper.updateBook(new Book("3","Python","1"));
//删除
mapper.deleteBook("3");
sqlSession.commit();
} finally {
sqlSession.close();
}
}
insert获取自增主键的值:
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys()
- useGeneratedKeys=“true”:使用自增获取主键值的策略
- keyProperty:指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的那个属性
测试:
<insert id="addBook" useGeneratedKeys="true" keyProperty="userId">
insert into book (userId,username,ustatus) values (#{userId},#{username},#{ustatus})
</insert>
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
//添加
Book book = new Book("3", "C#", "5");
mapper.addBook(book);
批量插入:
- 获取sqlSession时传入值,可以单独让mysql对着次会话进行批量插入
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
与spring整合时:
配置文件编写:
<!--配置批量执行的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
在Service中自动注入则可以使用批量:
@Autowired
private SqlSession sqlSessiom;
参数:
单个参数:
多个参数:
多个参数会被封装为一个map
命名参数:确定指定封装参数时map的key:@Param(“id”),多个参数会被封装为一个map
<select id="getBookOne" resultType="book">
select * from book where userId = #{userId} and username=#{username}
</select>
POJO:
Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
public Book getBookOne(Map<String,String> map);
Map<String,String> map=new HashMap<>();
map.put("userId","1");
map.put("username","java");
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book = mapper.getBookOne(map);
System.out.println(book);
<select id="getBookOne" resultType="book">
select * from book where userId = #{userId} and username=#{username}
</select>
TO:
如果多个参数不是业务模型中的数据,但是经常使用,推荐编写一个TO(Transfer Object)数据传输对象
- 如分页操作:
Page{
int index;
int size;
}
- 如分页操作:
Collection:
如果是Collection(List,Set)类型或者是数组,也会特殊处理,也就是把传入的list或者数组封装在map中
- key:Collection(collection),如果是List还可以使用(List),数组(array)
- public Book getBookById(List userId)
取值:取出第一个id的值:#{list[0]}
参数封装map的过程:(源码解析)
names:{0=id, 1=lastName};构造器的时候就确定好了
确定流程:<br />
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;<br />
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)<br />
name的值:<br />
标注了param注解:注解的值<br />
没有标注:<br />
1.全局配置:useActualParamName(jdk1.8):name=参数名<br />
2.name=map.size();相当于当前元素的索引<br />
{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<String, Object> param = new ParamMap<Object>();
int i = 0;
//4、遍历names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names集合的value作为key; names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)param
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
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;
}
}
}
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
#{key}就可以取出map中的值;
参数值获取:
和$的区别:
实例:
select from tbl_employee where id=${id} and last_name=#{lastName}
运行进去的sql语句: Preparing: select from tbl_employee where id=2 and last_name=?{}:可以获取map中的值或者pojo对象属性的值,#{}是以预编译的形式,将参数设置到sql语句中;使用PreparedStatement;防止sql注入问题,大多情况下,我们去参数的值都应该去使用#{};
{}是取出的值直接拼装在sql语句中;会有安全问题;
原生jdbc不支持占位符的地方我们就可以使用${}进行取值, 比如分表、排序,按照年份分表拆分:
select from ${year}_salary where xxx;
select from tbl_employee order by ${f_name} ${order}补充:sql注入问题
所谓SQL注入(sql inject),就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
{}更丰富的用法:
规定参数的一些规则:
javaType、 jdbcType、 mode(存储过程)、 numericScale、resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持;两种办法
1、#{email,jdbcType=OTHER};
2、jdbcTypeForNull=NULL
查询:
select返回list集合
测试:
- 接口中写方法
public List<Book> getBooks();
- 接口中写方法
- 映射文件中编写select语句
<!--如果返回的是一个集合,resultType写集合中元素的类型-->
<select id="getBooks" resultType="book">
select * from book
</select>
- 测试方法:
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne中有2个参数:
* 第一个参数为sql唯一标识
* 第二个为执行sql要使用的参数
*/
try {
//查询所有对象
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<Book> books = mapper.getBooks();
for(Book book:books){
System.out.println(book);
}
} finally {
sqlSession.close();
}
}
select返回map集合:
测试:
- 接口写方法
```java
//查询单个对象返回map
public Map
getBooksForMap(String userId);
- 接口写方法
```java
//查询单个对象返回map
public Map
//查询多个对象返回map
//多条记录封装在一个map,Map中的key是这条记录的主键,值是记录封装后的javaBean对象
//告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey(“userId”)
public Map
-
映射文件中编写select语句
```xml
<select id="getBooksForMap" resultType="Map">
select * from book where userId = #{userId}
</select>
<select id="getAllBookForMap" resultType="Map">
select * from book
</select>
测试方法:
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne中有2个参数:
* 第一个参数为sql唯一标识
* 第二个为执行sql要使用的参数
*/
try {
//查询对象已map返回
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Map<String, String> booksForMap = mapper.getBooksForMap("1");
System.out.println(booksForMap);
Map<String, Object> allBookForMap = mapper.getAllBookForMap();
System.out.println(allBookForMap);
} finally {
sqlSession.close();
}
}
resultMap自定义结果集映射规则:
测试:(
- 接口写方法:
//自定义映射结果查询
public Book getBookById(String userId);
- 接口写方法:
- 映射文件中编写select语句:
<resultMap id="myBook" type="book">
<!--指定的主键列的封装规则:
id:定义主键会底层优化
column:指定哪一列
property:指定对应的javaBean属性
-->
<id column="userid" property="userId"></id>
<!--定义普通列的封装规则
其他不指定的列会自动封装,我们只要写resultMap把全部的映射规则都写上 -->
<result column="username" property="username"></result>
<result column="ustatus" property="ustatus"></result>
</resultMap>
<!--resultMap:自定义结果集映射规则-->
<select id="getBookById" resultType="book">
select *from book where userid=#{userId}
</select>
- 测试方法:
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne中有2个参数:
* 第一个参数为sql唯一标识
* 第二个为执行sql要使用的参数
*/
try {
//查询返回自定义对象
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book bookById = mapper.getBookById("1");
System.out.println(bookById);
} finally {
sqlSession.close();
}
}
- 常用方法,查询的字段和javaBean中的字段不匹配需要经常使用。
关联查询:
级联属性封装结果:
数据库中创建两张表:
接口写方法:
//关联查询
public Book getBookAndEditionById(String userId);
- Book类中加入edition属性值: ```java package bean;
import org.apache.ibatis.type.Alias;
@Alias(value = “book”) public class Book { private String userId; private String username; private String ustatus; private Edition edition;
public Book(String userId, String username, String ustatus, Edition edition) {
this.userId = userId;
this.username = username;
this.ustatus = ustatus;
this.edition = edition;
}
public Edition getEdition() {
return edition;
}
public void setEdition(Edition edition) {
this.edition = edition;
}
public Book() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUstatus() {
return ustatus;
}
public void setUstatus(String ustatus) {
this.ustatus = ustatus;
}
@Override
public String toString() {
return "Book{" +
"userId='" + userId + '\'' +
", username='" + username + '\'' +
", ustatus='" + ustatus + '\'' +
", edition=" + edition +
'}';
}
}
-
映射文件中编写select语句:
<br />此时使用的是inner join on 内连接查询,也可以设定主键进行查询,也可以使用左连接查询
```xml
<resultMap id="myBookAndEdition" type="book">
<id column="userId" property="userId"></id>
<result column="username" property="username"></result>
<result column="ustatus" property="ustatus"></result>
<!--联合查询:级联属性封装结果集-->
<result column="editId" property="edition.editId"></result>
<result column="editName" property="edition.editName"></result>
</resultMap>
<select id="getBookAndEditionById" resultMap="myBookAndEdition">
select * from book b inner join edition e on b.deitId=e.editId where b.userid=#{userId};
</select>
- 测试:
@Test
public void MybatisTest() throws IOException {
//1.根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象
String resource = "Mybatis-Config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建SqlSession实例,能直接执行已经映射的sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne中有2个参数:
* 第一个参数为sql唯一标识
* 第二个为执行sql要使用的参数
*/
try {
//查询返回自定义对象
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book bookAndEditionById = mapper.getBookAndEditionById("1");
System.out.println(bookAndEditionById);
} finally {
sqlSession.close();
}
}
association分步查询:
- 方法一:将select标签进行替换:
<!--使用association定义关联的单个对象的封装规则:-->
<resultMap id="myBookAndEdition" type="book">
<id column="userId" property="userId"></id>
<result column="username" property="username"></result>
<result column="ustatus" property="ustatus"></result>
<!--association可以指定联合的javaBean对象
property="edition" ,指定哪个属性是联合的对象
javaType:指定这个属性对象的类型[不能省略]
-->
<association property="edition" javaType="edition">
<id column="editId" property="editId"></id>
<result column="editName" property="editName"></result>
</association>
</resultMap>
<select id="getBookAndEditionById" resultMap="myBookAndEdition">
select * from book b inner join edition e on b.deitId=e.editId where b.userid=#{userId};
</select>
方法二:创建查找edition的方法进行分布查询
- 创建接口方法:
public interface EditionMapper {
public Edition getEditionById(String editId);
}
- 创建接口方法:
- 映射文件:
<mapper namespace="mybatistest.EditionMapper">
<select id="getEditionById" resultType="edition">
select * from edition where editId=#{editId}
</select>
</mapper>
- Book的select标签:
<resultMap id="myBookAndEdition" type="book">
<id column="userId" property="userId"></id>
<result column="username" property="username"></result>
<result column="ustatus" property="ustatus"></result>
<!--association定义关联对象的封装规则
select:表明当前属性是调用select指定的方法查出的结果
column:指定将哪一列的值传给这个方法
流程:使用select指定的方法(传入column指定的这列的参数的值)查出对象,并封装给property指定的属性
-->
<association property="edition" select="mybatistest.EditionMapper.getEditionById" column="editId">
</association>
</resultMap>
<select id="getBookAndEditionById" resultMap="myBookAndEdition">
select * from book where userId=#{userId}
</select>
- edition的select标签:
<select id="getEditionById" resultType="edition">
select * from edition where editId=#{editId}
</select>
延时加载:
可以使用延时加载(懒加载):(按需加载)
我们每次查询Book对象的时候,都会将edition一起查询
延时加载可以使出版社信息在我们使用的时候再去查询
分段查询的基础上加上两个配置在全局配置文件中配置懒加载: | lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置
fetchType
属性来覆盖该项的开关状态。 | true | false | false | | —- | —- | —- | —- | | aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考lazyLoadTriggerMethods
)。 | true | false | false (在 3.4.1 及之前的版本中默认为 true) |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
新版本mybatis已经把aggressiveLazyLoading设置为false,不需要手动设置
- 测试:
Book bookAndEditionById = mapper.getBookAndEditionById("1");
System.out.println(bookAndEditionById.getEdition());
日志中显示调用2次select方法:
[mybatistest.BookMapper.getBookAndEditionById]-> Preparing: select from book where userId=?
[mybatistest.BookMapper.getBookAndEditionById]-> Parameters: 1(String)
[mybatistest.BookMapper.getBookAndEditionById]-<== Total: 1
[mybatistest.EditionMapper.getEditionById]-> Preparing: select from edition where editId=?
[mybatistest.EditionMapper.getEditionById]-> Parameters: 1(Integer)
[mybatistest.EditionMapper.getEditionById]-<== Total: 1
Book bookAndEditionById = mapper.getBookAndEditionById("1");
System.out.println(bookAndEditionById.getUserId());
日志中显示调用一次select方法:
[mybatistest.BookMapper.getBookAndEditionById]-> Preparing: select * from book where userId=?
[mybatistest.BookMapper.getBookAndEditionById]-> Parameters: 1(String)
[mybatistest.BookMapper.getBookAndEditionById]-<== Total: 1
collection定义关联集合查询:
- 创建edition类,里面创建list ```java package bean;
import java.util.List;
public class Edition {
private Integer editId;
private String editName;
private List
public Edition() {
}
public Edition(Integer editId, String editName, List<Book> books) {
this.editId = editId;
this.editName = editName;
this.books = books;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public Integer getEditId() {
return editId;
}
public void setEditId(Integer editId) {
this.editId = editId;
}
public String getEditName() {
return editName;
}
public void setEditName(String editName) {
this.editName = editName;
}
@Override
public String toString() {
return "Edition{" +
"editId=" + editId +
", editName='" + editName + '\'' +
", books=" + books +
'}';
}
}
-
接口中创建方法:
```java
public Edition getEditionAndBookListById(String editId);
映射文件中的select标签:
<resultMap id="editionAndBook" type="edition">
<id column="editId" property="editId"></id>
<id column="editName" property="editName"></id>
<!--
collection:定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型
-->
<collection property="books" ofType="book">
<id column="userId" property="userId"></id>
<result column="userName" property="username"></result>
<result column="ustatus" property="ustatus"></result>
</collection>
</resultMap>
<select id="getEditionAndBookListById" resultMap="editionAndBook">
select e.editId,e.editName,b.userId,b.username,b.ustatus from book b LEFT JOIN edition e on b.editId=e.editId where e.editId=#{editId};
</select>
- 测试方法:
EditionMapper mapper = sqlSession.getMapper(EditionMapper.class);
Edition editionAndBookListById = mapper.getEditionAndBookListById("1");
System.out.println(editionAndBookListById);
collection分步查询:
- Edition和Book接口的方法:
public Edition getEditionByIdAndBookByIdStep(String editId);
//根据字段返回Book的list集合
public List<Book> getBooksById(String editId);
映射文件中的select语句: ```xml
```xml
<resultMap id="booksById" type="book">
<id property="userId" column="userId"></id>
<result property="username" column="userName"></result>
<result property="ustatus" column="ustatus"></result>
</resultMap>
<select id="getBooksById" resultMap="booksById">
select * from book where editId=#{editId}
</select>
- 测试方法:
EditionMapper mapper = sqlSession.getMapper(EditionMapper.class);
Edition editionAndBookListById = mapper.getEditionByIdAndBookByIdStep("1");
System.out.println(editionAndBookListById);
- 扩展:多列的值传递过去,将多列的值封装map传递,column=“{key1=column1,key2=column2}”
<collection property="books" select="mybatistest.BookMapper.getBooksById" column="editId=editId">
</collection>
- collection也可以使用延时加载,可以不用修改全局配置
fetchType=“lazy” ,使用延时查询
fetchType=“eager”,使用立即查询
discriminator鉴别器:
- 可以根据列的值判断返回的对象:
<resultMap id="editionAndBooks" type="edition">
<id column="editId" property="editId"></id>
<result column="editName" property="editName"></result>
<!--
column:指定判断的列名
javaType:列值对应的java类型
-->
<discriminator javaType="string" column="editId">
<case value="1" resultType="edition">
<collection property="books" select="mybatistest.BookMapper.getBooksById" column="editId">
</collection>
</case>
<case value="2" resultType="edition">
<id column="editId" property="editId"></id>
<result column="editName" property="editName"></result>
</case>
</discriminator>
</resultMap>
<select id="getEditionByIdAndBookByIdStep" resultMap="editionAndBooks">
select * from edition where editId=#{editId}
</select>
动态sql:
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if判断:
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。
<resultMap id="myBook" type="book">
<id column="userid" property="userId"></id>
<result column="userName" property="username"></result>
<result column="ustatus" property="ustatus"></result>
</resultMap>
<select id="getBookById" resultMap="myBook">
select *from book
<if test="userId!=null">
where userId like #{userId}
</if>
<if test="userName!=null">
and userName like #{userName}
</if>
<if test="ustatus!=null">
and ustatus like #{ustatus}
</if>
</select>
使用and连接可以进行2个条件判断
<if test="userName!=null and ustatus!=null">
and userName like #{userName}
</if>
- 测试:
Book bookById = mapper.getBookById("1","Java",null);
System.out.println(bookById);
当输入为null时,里面if标签的sql就不会在数据库中执行。
查询的时候如果某些条件没带可能sql拼装会有问题
给where 后面加上1=1,以后的条件都and xxx ```xml
-
mybatis使用where标签来将所有的查询条件包括在内,mybatis就会将where标签中多出来的and或者or去掉,注意:where只会去掉第一个多出来的and或者or,and放在后面sql语句错误
```xml
<select id="getBookById" resultMap="myBook">
select *from book
<where>
<if test="userId!=null">
userId like #{userId}
</if>
<if test="userName!=null ">
and userName like #{userName}
</if>
<if test="ustatus!=null">
and ustatus like #{ustatus}
</if>
</where>
</select>
trim自定义截取:
后面多出的and或者or,where标签不能解决
prefix=“ ”:前缀,trim标签体是整个字符串拼串后的结果。
prefix给拼串后的整个字符串加一个前缀。prefixOverrides=“ ”:前缀覆盖
prefixOverrides去掉整个字符串前面多余的字符。suffix=“ ”:后缀
suffix给拼串后的整个字符串加一个后缀。suffixOverrides=“ ”:后缀覆盖
suffixOverrides去掉整个字符串后面多余的字符。
<select id="getBookById" resultMap="myBook">
select *from book
<trim prefix="where" suffixOverrides="and">
<if test="userId!=null">
userId like #{userId} and
</if>
<if test="userName!=null ">
userName like #{userName} and
</if>
<if test="ustatus!=null">
ustatus like #{ustatus} and
</if>
</trim>
</select>
choose分支选择:
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="getBookByChoose" resultType="book">
select * from book
<where>
<choose>
<when test="userId!=null">
userId=#{userId}
</when>
<when test="userName!=null">
userName=#{userName}
</when>
<when test="ustatus!=null">
ustatus=#{ustatus}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
set和if实现动态更新:
- 创建接口
public void updateBySet(Book book);
- 配置映射文件
<update id="updateBySet">
update book
<set>
<if test="userId!=null">
userId=#{userId},
</if>
<if test="username!=null">
username=#{username},
</if>
<if test="ustatus!=null">
ustatus=#{ustatus}
</if>
where userId=#{userId}
</set>
</update>
- 测试:
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
mapper.updateBySet(new Book("3","Python","m"));
sqlSession.commit();
foreach遍历集合:
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。
- item:将当前遍历出的元素赋值给指定的变量
- separator:每个元素之间的分隔符
- open:遍历出所有结果拼接一个结束的字符串
- close:遍历出所有结果拼接一个结束的字符
当使用可迭代对象或者数组时
- index 是当前迭代的序号
- item 的值是本次迭代获取到的元素
当使用 Map 对象(或者 Map.Entry 对象的集合)时
- index 是键
- item 是值
Demo:
- 接口方法:
public List<Book> getBookByForeach(List<String> userId);
- 接口方法:
- 映射文件中查询标签:
<select id="getBookByForeach" resultType="book">
select * from book where userId in
<foreach collection="list" item="item_id" separator=","
open="(" close=")">
#{item_id}
</foreach>
</select>
- 测试:
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
List<String> userIds=new ArrayList<>();
userIds.add("1");
userIds.add("2");
List<Book> bookByForeach = mapper.getBookByForeach(userIds);
for (Book book:bookByForeach){
System.out.println(book);
}
foreach批量插入的两种方式:
- 第一种(推荐):
<insert id="addBooks">
insert into book (userId,username,ustatus) values
<foreach collection="list" item="book" separator=",">
(#{book.userId},#{book.username},#{book.ustatus})
</foreach>
</insert>
- 第二种:
<insert id="addBooks">
<foreach collection="list" item="book" separator=";">
insert into book (userId,username,ustatus) values (#{book.userId},#{book.username},#{book.ustatus})
</foreach>
</insert>
但需要再jdbc配置文件中设置属性支持多次插入:pro.url=jdbc:mysql://localhost:3306/user?allowMultiQueries=true
- 测试:
@Test
public void test2() throws IOException {
SqlSessionFactory sqlSessionFactory = sqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper;
List<Book> books;
try {
mapper = sqlSession.getMapper(BookMapper.class);
books = new ArrayList<>();
books.add(new Book("6","Javascrip","j"));
books.add(new Book("7","go","g"));
mapper.addBooks(books);
} finally {
sqlSession.commit();
}
}
内置参数:
_parameter:代表整个参数
- 单个参数:_parameter就是这个参数
- 多个参数:参数会被封装为一个map:_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签
- _databaseId就是代表当前数据库别名oracle,可以用来使用不同数据库执行不同的sql操作
<select id="getbook" resultType="book">
select * from book
<if test="_paramter!=null">
where userId=#{userId}
</if>
</select>
//测试_parameter内置参数
public List<Book> getbook(Book book);
_parameter代表传入的对象,要使用对象中的内容何以直接获取,也可以通过_parameter.属性值获取bind绑定:
bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文,方便后文引用。使用like查询满足一个字符等等操作
name写你拼接的变量
value 写你要如何拼接,注意使用 ‘ ’ 来表示拼接的字符<select id="getbook" resultType="book">
<bind name="username" value="username+'%'"/>
select * from book
<if test="_parameter!=null">
where username like #{username}
</if>
</select>
sql抽取可重用片段:
抽取可重用的sql片段,方便后面引用
- sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
- include来引用已经抽取的sql
- include还可以自定义一些property,sql标签内部就能使用自定义的属性,include-property,取值的正确方式${prop},#{不能使用这种方式}
- Demo:
```xml
insert into book (
<include refid="insertColumns"></include>
) values
(#{book.userId},#{book.username},#{book.ustatus})
userId,username,ustatus <a name="7266dd6f"></a>
# Mybatis的缓存机制:
- 
<a name="c3ab9156"></a>
### 两种缓存:
-
两级缓存:
- 一级缓存:(本地缓存)sqlSession级别缓存。其实就是sqlSession级别的一个Map。与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
- 一级缓存失效原因(没有使用到当前一级缓存的情况,效果就是,还需要向数据库发出查询)
1. sqlSession不同
2. sqlSession相同,查询条件不同,(当前一级缓存中还没有这个数据)
3. sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据库有影响)
4. sqlSession相同,手动清楚了一级缓存(缓存清空 sqlSessin.clearcache())
-
二级缓存:
-
一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中,如果会话关闭,一级缓存中的数据就会被保存到二级缓存中,就可以参照二级缓存中的内容,不同的namespace查出的数据会放在自己对应的缓存中(map)
-
效果:数据会从二级缓存中获取,查出的数据会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存的数据才会转移到二级缓存中
-
使用:
1.
开启全局二级缓存配置:
```xml
<setting name="cacheEnabled" value="true"/>
2.
去mapper.xml中配置使用二级缓存
<mapper namespace="mybatistest.BookMapper">
<cache></cache>
可以自己设置二级缓存机制:-
eviction(缓存的回收策略)
- `LRU` – 最近最少使用:移除最长时间不被使用的对象。
- `FIFO` – 先进先出:按对象进入缓存的顺序来移除它们。
- `SOFT` – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- `WEAK` – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
-
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
-
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
-
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
-
type=“ ”:指定自定义缓存的全类名,实现Cache接口即可
3.
我们的POJO需要实现序列化接口
public class Book implements Serializable {
4.
测试:
SqlSession sqlSession = sqlSessionFactory.openSession();
BookMapper mapper = sqlSession.getMapper(BookMapper.class);
Book book1 = mapper.getBook("1");
System.out.println(book1);
sqlSession.close();
SqlSession sqlSession1=sqlSessionFactory.openSession();
BookMapper mapper1 = sqlSession1.getMapper(BookMapper.class);
Book book2 = mapper1.getBook("1");
System.out.println(book2);
System.out.println(book1==book2);
sqlSession1.close();
和缓存有关的设置/属性
cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)
每个select标签都有useCache=“true”; false:不适用缓存(一级缓存依然使用,二级缓存不使用)
每个增删改标签的:flushCache=“true” (一级二级都会清除)
增删改执行完成后就会清除缓存;
测试:flushCache=“true”:一级缓存就清空了,二级也会被清除
查询标签:flushCache=“false”:如果flushCache=true每次查询都会清空缓存,缓存是没有被使用sqlSession.clearCache();只是清除当前session的一级缓存
localCacheScope:本地缓存作用域(一级缓存session);当前会话所有数据保存在会话缓存中, 取值STATEMENT:可以禁用一级缓存
缓存原理图解:
与第三方包整合(ehcache)
- 导入第三方缓存包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.8</version>
</dependency>
- 导入与第三方缓存整合的适配包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
- 在类路径下加入ehcache的配置文件:ehcache.xml
```xml
<ehcache xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“<?xml version="1.0" encoding="UTF-8"?>
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
-
mapper.xml中使用自定义缓存
```xml
<mapper namespace="mybatistest.BookMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
- 图解缓存机制:
Mybatis逆向工程:
简介:
文档地址:
http://mybatis.org/generator/index.html导入依赖:
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
</dependency>
- 配置文件:
```xml
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--javaModelGenerator:指定javaBean的生成策略
targetPackage:目标包名
targetProject:目标工程
-->
<javaModelGenerator targetPackage="bean" targetProject=".\.\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--sqlMapGenerator:sql映射生成策略-->
<sqlMapGenerator targetPackage="resources" targetProject=".\.\src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!--javaClientGenerator:指定mapper接口所在的位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject=".\.\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!--table指定要逆向分析哪张表:根据表创建javaBean
tableName表明
domainObjectName创建javaBean对象类名
-->
<table tableName="book" domainObjectName="book"></table>
<table tableName="edition" domainObjectName="edition"></table>
</context>
-
运行测试类:
```java
package test;
import org.junit.Test;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class MybatisTest {
@Test
public void testMbg() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("src/main/resources/mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}
则会逆向自动生成bean,mapper,接口方法等…Mybatis运行原理:
sqlSessionFactory的初始化:(根据配置文件创建sqlSessionFactory)
总之,把配置文件的信息解析并保存再Configuration对象中,返回并包含了Configuration的DefaultSqlSessionopenSession创建sqlSession对象:
返回sqlSession的实现类DefaultSqlSession对象。它里面包含了Executer和Configuration:Executer会在这一步被创建查询实现:
总流程:
- 根据配置文件(全局,sql映射)初始化Configuration对象
- 创建一个DefaultSqlSession对象,它里面包含Configuration以及Executor(根据全局配置文件的defaultExecuterType创建出对应的Executor)
- DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy
- MapperProxy里面有(DefaultSqlSession)
执行增删查改的方法:
- 调用DefaultSqlSeeion的增删查改(Executor)
- 会创建一个StatementHandler对象(同时也会创建ParameterHandler和ResultRetHandler)
- 调用StatementHandler来预编译参数以及设置参数值,使用ParamterHandler来给sql设置参数
- 调用StatementHandler的增删查改的方法
- ResultSetHandler封装结果
Mybatis插件:
插件原理:
在四大对象创建的时候:
每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandle);
获取到所有的Interceptor(拦截器)(插件需要实现的接口),调用interceptor.plugin(target);返回target包装后的对象
插件机制:我们可以使用插件为目标对象创建一个代理对象:AOP(面向切面)
我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行
插件编写:
单个插件:
- 编写Interceptor的实现类
- 使用@Intercepts注解完成插件签名
- 将写好的插件注册到全局配置文件中
package mybatistest;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
/**
* 完成插件签名:
* 告诉mybatis当前插件用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)
}
)
public class MyFirstPlugin implements Interceptor {
/**
* 拦截目标对象的目标方法的执行
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("intercept方法:"+invocation.getMethod());
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:包装目标对象,为目标对象创建代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("plugin方法:"+target);
//我们可以借助Plugin的wrap方法来使用Interceptor包装我们目标对象
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
}
/**
* setProperties:将插件注册时的property属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件的配置信息"+properties);
}
}
全集配置文件中plugins标签则是传入里面需要使用的参数
<plugins>
<plugin interceptor="mybatistest.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
插件会产生目标对象的代理对象:
多个插件:
多个插件就会产生多层代理
创建动态代理的时候,是按照插件配置顺序创建层层代理对象。
执行目标方法的之后,按照逆向顺序执行
PageHelper分页插件:
插件文档链接:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
使用插件:
- 导入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
- 导入依赖:
- 配置拦截器信息:
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
- 使用接口方法来进行测试:
(主要演示导航页号)@Test
public void pageHelperTest() throws IOException {
SqlSessionFactory sqlSessionFactory=sqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
BookMapper mapper = session.getMapper(BookMapper.class);
try {
PageHelper.startPage(3, 2);
List<Book> books = mapper.getBooks();
//使用pageInfo获取分页的属性,用PageInfo对结果进行包装
PageInfo page = new PageInfo(books,4);
for (Book book:books){
System.out.println(book);
}
int[] navigatepageNums = page.getNavigatepageNums();
for (int i = 0; i < navigatepageNums.length; i++) {
System.out.println(navigatepageNums[i]);
}
} finally {
session.close();
}
}
使用PageInfo来进行封装:
注:PageInfo中的属性:private static final long serialVersionUID = 1L;
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示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 = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;
可以通过PageInfo对象获取属性值,但使用导航页面时需要在在构造时传入参数。自定义类型处理枚举类:
编写枚举类 ```java package mybatistest;
public enum BookEnum { LOGIN(100,”用户登录”),LOGINOUT(200,”用户登出”),REMOVE(300,”用户不存在”); private Integer code; private String msg;
BookEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
//按照状态码返回枚举对象
public static BookEnum getBookStatusByCode(Integer code){
switch (code){
case 100:
return LOGIN;
case 200:
return LOGINOUT;
case 300:
return REMOVE;
default:
return LOGINOUT;
}
}
}
-
在Bean和数据库表中创建相应的属性和字段:
```java
package bean;
import mybatistest.BookEnum;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
@Alias(value = "book")
public class Book implements Serializable {
private String userId;
private String username;
private String ustatus;
private BookEnum bookEnum=BookEnum.LOGINOUT;
public Book() {
}
public Book(String userId, String username, String ustatus, BookEnum bookEnum) {
this.userId = userId;
this.username = username;
this.ustatus = ustatus;
this.bookEnum = bookEnum;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUstatus() {
return ustatus;
}
public void setUstatus(String ustatus) {
this.ustatus = ustatus;
}
@Override
public String toString() {
return "Book{" +
"userId='" + userId + '\'' +
", username='" + username + '\'' +
", ustatus='" + ustatus + '\'' +
", bookEnum=" + bookEnum +
'}';
}
}
- 全局配置文件中配置相应的TypeHandler
<!--配置自定义的类型处理器-->
<typeHandlers>
<typeHandler handler="mybatistest.MyEnumBookStatusTypeHandler" javaType="mybatistest.BookEnum"></typeHandler>
</typeHandlers>
也可以在处理莫格字段的时候告诉Mybatis用什么类型处理器保存:#{bookEnum,typeHandler=xxx}
查询:
<resultMap id="MyBook" type="bean.Book">
...
<result column="bookEnum" property="bookEnum" typeHandler="mybatistest.MyEnumBookStatusTypeHandler"></result>
</resultMap>
注意:如果在参数位置修改TypeHandler,应该保证数据和查询用的TypeHandler是一样的。