MyBatis学习笔记
1、MyBatis简介
1.1 什么是MyBatis
- 持久层框架;
- 几乎避免所有的JDBC代码和手动设置参数以及获取结果集的过程;
- 使用简单的XML或注解来配置和映射原生信息,将接口和Java的实体类映射为数据库中的记录;
- MyBatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
- GitHUb:https://github.com/mybatis/mybatis-3
1.2 持久化
- 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。
- 内存断电后数据会丢失;
- 内存过于昂贵;
1.3 持久层
- 完成持久化工作的代码层;
1.4 MyBatis的优点
- 简单易学;
- 灵活:不会对应用程序或者数据库的现有设计强加任何影响;SQL语句写在XML里,便于统一管理和优化;
- 解除SQL语句与程序代码的耦合:通过提供DAO层,将业务逻辑与数据访问逻辑分离。是系统的设计更加清晰,更易维护,更易单元测试;
- 提供XML标签,支持编写动态SQL;
2、使用MyBatis的步骤
2.1 搭建环境
- 进行数据库准备工作;
2.2 导入MyBatis
- 导入相关jar包;
2.3 编写代码
- 编写MyBatis核心配置文件;
- 编写MyBatis工具类,核心代码如下:
public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //获取SqlSession连接 public static SqlSession getSession(){ return sqlSessionFactory.openSession(); } }
- 创建实体类,对应数据库表结构;
- 编写Mapper接口;
- 编写Mapper.xml;
2.4 测试
- 编写测试类;
3、CRUD操作
3.1 namespace
- XML中namespace为接口完整包名;
3.2 select、insert、update、delete
- 增删改操作需要提交事务;
- 编写模糊查询Like语句时,应在Java语句中添加通配符,而不是在SQL中拼接通配符,后者可能引起SQL注入;
4、配置解析
4.1 核心配置文件
- mybatis-config.xml 系统核心配置文件
- MyBatis 的配置文件包含了影响 MyBatis 行为的设置和属性信息。
- 能配置的内容如下:
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器) <!-- 注意元素节点的顺序!顺序不对会报错 -->
4.2 environments元素
- 可以配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定);
- 子元素节点:environment,具体的一套环境,通过设置id进行区别,id保证唯一!
- 子元素节点:数据源(dataSource)使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。数据源是必须配置的。有三种内建的数据源类型:
unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。 pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。 jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
- 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…
4.3 mappers元素
- mappers:映射器,定义映射SQL语句文件,告诉 MyBatis 到哪里去找到这些语句;
- 引入资源方式:
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
<!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers>
<!-- 使用映射器接口实现类的完全限定类名 需要配置文件名称和接口名称一致, 并且位于同一目录下 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 但是需要配置文件名称和接口名称一致, 并且位于同一目录下 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
- Mapper文件
namespace和子元素的id联合保证唯一 , 区别不同的mapper;<?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="com.kuang.mapper.UserMapper"> </mapper>
namespace的命名必须跟某个接口同名;
接口中的方法与映射文件中sql语句id应该一一对应;
namespace命名规则 : 包名+类名;
4.4 Properties优化
- 在资源目录下新建一个db.properties:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis? useSSL=true&useUnicode=true&characterEncoding=utf8 username=root password=123456
- 将文件导入properties 配置文件:
<configuration> <!--导入properties文件--> <properties resource="db.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
4.5 typeAlias优化
- 类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
<typeAliases> <typeAlias type="com.kuang.pojo.User" alias="User"/> </typeAliases>
4.6 settings
- 官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings
- 一个完整的settings元素的示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
4.7 typeHandlers
4.8 objectFactory
5、生命周期与作用域
5.1 MyBatis执行过程
5.2 作用域(scope)
- SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
- SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
- 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。
- 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。所以 SqlSession 的最佳的作用域是请求或方法作用域
6、ResultMap
- 要解决的问题:属性名和字段名不一致
6.1 解决方案
- 为列名指定别名;
- 采用ResultMap:
<resultMap id="UserMap" type="User"> <!-- id为主键 --> <id column="id" property="id"/> <!-- column是数据库表的列名 , property是对应实体类的属性名 --> <result column="name" property="name"/> <result column="pwd" property="password"/> </resultMap> <select id="selectUserById" resultMap="UserMap"> select id , name , pwd from user where id = #{id} </select>
7、日志
7.1 日志工厂
- Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序
查找)。 如果一个都未找到,日志功能就会被禁用。<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
7.2 Log4j
- Log4j是Apache的一个开源项目;
- 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件…
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- 使用步骤:
a. 导入log4j的包:
b. 配置文件编写:<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
c. setting设置日志实现:将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/kuang.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%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
d. 在程序中使用Log4j进行输出;<settings> <setting name="logImpl" value="LOG4J"/> </settings>
e. 测试;
8、分页
8.1 为什么需要分页?
如果查询大量数据的时候,使用分页进行查询,可以每次处理小部分数据,这样对数据库压力就在可控范围内。
8.2 使用Limit进行分页
- 修改Mapper文件:
<select id="selectUser" parameterType="map" resultType="user"> select * from user limit #{startIndex},#{pageSize} </select>
- Mapper接口,参数为map:
//选择全部用户实现分页 List<User> selectUser(Map<String,Integer> map);
- 在测试类中传入参数测试,起始位置 = (当前页面 - 1 ) * 页面大小:
//分页查询 , 两个参数startIndex , pageSize @Test public void testSelectUser() { SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); int currentPage = 1; //第几页 int pageSize = 2; //每页显示几个 Map<String,Integer> map = new HashMap<String,Integer>(); map.put("startIndex",(currentPage-1)*pageSize); map.put("pageSize",pageSize); List<User> users = mapper.selectUser(map); for (User user: users){ System.out.println(user); } session.close(); }
8.3 RowBounds分页
- mapper接口:
//选择全部用户RowBounds实现分页 List<User> getUserByRowBounds();
- mapper文件:
<select id="getUserByRowBounds" resultType="user"> select * from user </select>
- 测试类,在这里,我们需要使用RowBounds类:
@Test public void testUserByRowBounds() { SqlSession session = MybatisUtils.getSession(); int currentPage = 2; //第几页 int pageSize = 2; //每页显示几个 RowBounds rowBounds = new RowBounds((currentPage- 1) * pageSize, pageSize); //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了] List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds); for (User user: users){ System.out.println(user); } session.close(); }
8.4 PageHelper
9、使用注解开发
利用注解开发就不需要mapper.xml映射文件了。
9.1 使用步骤
- 我们在我们的接口中添加注解:
//查询全部用户 @Select("select id,name,pwd password from user") public List<User> getAllUser();
- 在mybatis的核心配置文件中注入:
<!--使用class绑定接口--> <mappers> <mapper class="com.kuang.mapper.UserMapper"/> </mappers>
- 我们去进行测试:
@Test public void testGetAllUser() { SqlSession session = MybatisUtils.getSession(); //本质上利用了jvm的动态代理机制 UserMapper mapper = session.getMapper(UserMapper.class); ● List<User> users = mapper.getAllUser(); for (User user : users){ System.out.println(user); } session.close(); }
9.2 本质上利用了jvm的动态代理机制
9.3 Mybatis详细的执行流程
9.4 使用注解增删改查
9.5 关于@Param
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
a. 在方法只接受一个参数的情况下,可以不使用@Param。
b. 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
c. 如果参数是 JavaBean , 则不能使用@Param。
d. 不使用@Param注解时,参数只能有一个,并且是Javabean。
9.6 #与$的区别
- “#{}“的作用主要是替换预编译语句(PrepareStatement)中的占位符”?” 【推荐使用】
INSERT INTO user (name) VALUES (#{name}); INSERT INTO user (name) VALUES (?);
- "${}"的作用是直接进行字符串替换,可能引起SQL注入
INSERT INTO user (name) VALUES ('${name}'); INSERT INTO user (name) VALUES ('Kuo-Teng');
10、一对多与多对一的处理
10.1 按查询嵌套处理;
10.2 按结果嵌套处理;
11、动态SQL
官方文档:https://mybatis.org/mybatis-3/zh/dynamic-sql.html
12、缓存
12.1 简介
- 什么是缓存 [ Cache ]?
存在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。 - 为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率。 - 什么样的数据能使用缓存?
经常查询并且不经常改变的数据。
12.2 Mybatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率;
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存;
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存);
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存;
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存;
12.3 一级缓存
- 一级缓存也叫本地缓存:与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
12.4 一级缓存的失效
- 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
- 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
a. sqlSession不同;
b. sqlSession相同,查询条件不同;
c. sqlSession相同,两次查询之间执行了增删改操作;
d. sqlSession相同,手动清除一级缓存;
12.5 二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
a. 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
b. 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
c. 新的会话查询信息,就可以从二级缓存中获取内容;
d. 不同的mapper查出的数据会放在自己对应的缓存(map)中; - 官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
12.6 结论
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据;
- 查出的数据都会被默认先放在一级缓存中;
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中;