写在最前面
这篇笔记是看秦老师的 Mybatis 视频跟着写的, 建议去看视频学习,能掌握更多的内容
B站链接在此:
视频链接
https://www.bilibili.com/video/BV1NE411Q7Nx
主页链接
https://space.bilibili.com/95256449
1、简介
1.1 什么是Mybatis
- MyBatis 是一款优秀的持久层框架,
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
1.2 如何获得Mybatis?
-
maven仓库:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
-
GitHub: https://github.com/mybatis/mybatis-3/releases
-
中文文档: https://mybatis.org/mybatis-3/zh/index.html
1.3 持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时时态转换的过程
- 内存: 断电即失
为什么需要数据持久化
- 一些数据不能丢弃
- 内存太贵了
1.4持久层
Dao层, Service层. …
- 完成持久化工作的代码块
- 层次明显
1.5为什么需要Mybatis?
- 帮助程序员将数据存入数据库中
- 方便
- 传统的JDBC代码复杂, 通过框架简化。
- 更容易上手
- 优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
- 使用的人多,
2、第一个Mybatis程序
思路: 搭建环境—> 导入Mybatis–> 编写代码–>测试
2.1搭建环境
搭建数据库
CREATE Database `mybatis`;
USE `mybatis`;
create table `user`(
`id` INT(20) not null primary key,
`name` varchar(30) DEFAULT null,
`pwd` varchar(30) DEFAULT null
) engine=INNODB DEFAULT CHARSET=utf8;
insert into `user` values
(1,'xiangming','123456'),
(2,'xiangsing','123456'),
(3,'xiangaing','123456'),
(4,'xiangding','123456')
新建项目
-
新建一个普通的maven项目
-
删除src目录
-
导入maven依赖
<!--导入依赖--> <dependencies> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--Mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!--Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies>
2.2创建子模块
-
创建一个子模块Mybatis01
-
编写核心配置文件
<?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 core configure--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration>
-
编写Mybatis工具类
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
package com.qwrdxer.utils;
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 java.io.IOException;
import java.io.InputStream;
//sqlSessionFactory -->sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {//第一步 获取sqlSessionFactory对象
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.3 编写代码
-
实体类
package com.qwrdxer.pojo; public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { final StringBuilder sb = new StringBuilder("{"); sb.append("\"id\":") .append(id); sb.append(",\"name\":\"") .append(name).append('\"'); sb.append(",\"pwd\":\"") .append(pwd).append('\"'); sb.append('}'); return sb.toString(); } }
-
DAO接口
package com.qwrdxer.dao; import com.qwrdxer.pojo.User; import java.util.List; public interface UserDao { List<User> getUserList(); }
-
实现类(由原来的UserDaoImpl实现类转换为 一个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"> <!--namespace bind Dao/mapper interface--> <mapper namespace="com.qwrdxer.dao.UserDao"> <!--select --> <select id="getUserList" resultType="com.qwrdxer.pojo.User" > select * from mybatis.user </select> </mapper>
2.4测试
注意点:
org.apache.ibatis.binding.BindingException: Type interface com.qwrdxer.dao.UserDao is not known to the MapperRegistry. 需 要在核心配置文件中配置 Mapper.xml位置
mapper.xml不能有中文注释
maven由于他的约定大于配置,我们之后可以能遇到我们写的配置文件,无法被导出或者生效的问题,解决方案:
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
- junit测试
在test重新创建文件
package com.qwrdxer.dao;
import com.qwrdxer.pojo.User;
import com.qwrdxer.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
try{
//获取Sqlsession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式1. getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//方式2. 不推荐
List<User> userList1 = sqlSession.selectList("com.qwrdxer.dao.UserDao.getUserList");
for (User user : userList1) {
System.out.println(user);
}
}catch(Exception e){
}finally{
//关闭Sqlsession
sqlSession.close();
}
}
}
-
在mybatis-config.xml注册mapper
<mappers> <mapper resource="com/qwrdxer/dao/UserMapper.xml"></mapper> </mappers>
-
可能遇到的问题:
- 配置文件没有注册
- 绑定接口错误
- 方法名不对
- 返回类型不对
- maven导出资源未配置
3、CRUD(增删改查)
3.1 namespace
namespace中的包名要和dao/mapper接口包名一致
select
选择,查询语句
<select id="getUserList" resultType="com.qwrdxer.pojo.User" >
select * from mybatis.user
</select>
- id就是对应namespace中的方法名
- resultType: Sql语句执行的返回值类型
- parameterType: 参数类型
insert
//接口
int addUser(User user);
// xml配置
<insert id="addUser" parameterType="com.qwrdxer.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
//测试
//增删改需要提交事务
@Test
public void Insert(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int xiaobai = mapper.addUser(new User(11, "xiaobai", "123456"));
sqlSession.commit();//提交事务
System.out.println(xiaobai);
sqlSession.close();
}
update
<update id="updateUser" parameterType="com.qwrdxer.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd } where id=#{id};
</update>
map和模糊查询
当实体类属性太多时,直接构造 实体类对象作为参数过于麻烦,使用map构造需要传入的相关参数, 可以简化操作
//xml文件. 对于map对象, 只要键值对的键 和xml文件中的变量一致即可
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,name,pwd) values (#{userid},#{userName},#{password});
</insert>
//测试类
@Test
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
// insert into mybatis.user (id,name,pwd) values (#{userid},#{userName},#{password});
map.put("userid",13);
map.put("userName","小红");
map.put("password","12345678");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
- Map传递参数,直接在sql中取出key即可![parmeterType=“map”]
- 对象传递参数,直接在sql中取对象即可! [parmeterType=“实体类”]
- 只有一个基本类型的情况下, 可以直接在sql中取到 [parmeterType=“int /String” 可以不写]
- 多个参数 使用Map, 或者注解!
模糊查询(扩展)
//模糊查询
List<User> getUserLike(String value);
//xml配置
<select id="getUserLike" resultType="com.qwrdxer.pojo.User">
select * from mybatis.user where name like "%"#{value}"%"
</select>
//测试代码
//模糊查询
@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> xia = mapper.getUserLike("xia");
for (User user : xia) {
System.out.println(user.toString());
}
sqlSession.close();
}
4.配置解析
4.1核心配置文件
-
核心配置文件官方推荐命名是 mybatis-config.xml
-
MyBatis的配置文件包含了会深深影响Mybatis行为的设置和属性
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
4.2 环境配置
尽管可以配置多个环境, 但是每个SQLSessionFactory实例只能选择一种环境(配置文件)
注意一些关键点:
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
事务管理器(了解)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为
数据源(了解 POOLED)
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
driver
– 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。defaultNetworkTimeout
– 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看java.sql.Connection#setNetworkTimeout()
的 API 文档以获取更多信息。作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:
driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为
UTF8
的encoding
属性给数据库驱动。POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
– 在任意时间可存在的活动(正在使用)连接数量,默认值:10poolMaximumIdleConnections
– 任意时间可能存在的空闲连接数。poolMaximumCheckoutTime
– 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait
– 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
– 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections
与poolMaximumLocalBadConnectionTolerance
之和。 默认值:3(新增于 3.4.5)poolPingQuery
– 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。poolPingEnabled
– 是否启用侦测查询。若开启,需要设置poolPingQuery
属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor
– 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。data_source
– 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
Mybatis 默认的事务管理器是JDBC , 数据源:连接池 POOLED
4.3 属性(properties)
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
注: 在XML文件中所有的标签都可以规定其顺序
- 手动编写一个db.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
-
在核心配置文件中引入 ,即可使用
<properties resource="db.properties"></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="${passwd}"/> </dataSource> </environment> </environments>
-
在properties标签中也可以直接配置propertiy, 这种内部的属性会在外部属性文件引入后 ,产生同名属性覆盖(即优先使用外部文件配置)。
4.4类型别名(typeAliases)
- 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias type="com.qwrdxer.pojo.User" alias="User"></typeAlias>
</typeAliases>
当这样配置时,User
可以用在任何使用 com.qwrdxer.pojo.User 的地方。
- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.qwrdxer.pojo"/>
</typeAliases>
每一个在包 com.qwrdxer.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名,。
比如包下的User实体类, 可以使用user作为别名引用.
若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
-
第一种使用可以自定义名字, 第二种不行, 如果非要修改, 需要使用注解
-
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 映射的类型 _byte byte _long long _short short _int int _integer int _double double _float float _boolean boolean string String byte Byte long Long short Short int Integer integer Integer double Double float Float boolean Boolean date Date decimal BigDecimal bigdecimal BigDecimal object Object map Map hashmap HashMap list List arraylist ArrayList collection Collection iterator Iterator
4.5设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
https://mybatis.org/mybatis-3/zh/configuration.html#settings
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
---|---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
一个配置完整的 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.6 其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins插件
- Mybatis-generator-core
- mybatis-plus
- 通用Map普洱
4.7 映射器(Mappers)
MapperRegistry :注册绑定Mapper文件
<mappers>
<mapper resource="com/qwrdxer/dao/UserMapper.xml"></mapper>
</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>
方式二: 使用class文件绑定注册
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意点:
- 接口和他的Mapper必须同名
- 接口和他的Mapper必须在同一个包下
方式三:使用扫描包进行注入绑定 (在Mapper多时推荐使用)
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
注意点:
-
接口和他的Mapper必须同名
-
接口和他的Mapper必须在同一个包下
-
也可以通过在resource文件下建立同样的包路径, 将xml文件放入实现分离(JVM)
4.8 生命周期和作用域
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 一旦创建了sqlSessionFactory, 就不在需要他了
- 是局部变量
SqlSessionFacotory:
- 可以想象为数据库连接池
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession:
- 连接到连接池的一个请求
- 需要被关闭, 防止资源被占用
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 比如在web中, 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。
这里面的每一个mapper 就代表一个具体的业务
5、解决属性名和字段名不一致的问题
1.问题描述
数据库中字段
测试实体类不一致的情况( 数据库为pwd 实体类为password)
public class User {
private int id;
private String name;
private String password;
返回结果为空:
select * from mybatis.user where id =#{i}
↓类转换器↓
select id,name,pwd from mybatis.user where id =#{i}
2,解决方法
解决方法:
- 起别名select id,name,pwd as password from mybatis.user where id =#{i}
{"id":2,"name":"xiangsing","password":"123456"}成功
-
使用resultmap结果集映射
id name pwd id name password
<!--结果集映射--> <resultMap id="UserMap" type="com.qwrdxer.pojo.User"> <result column="id" property="id"></result> <result column="name" property="name"></result> <result column="pwd" property="password"></result> </resultMap> <select id="getUserById" resultType="UserMap" parameterType="int"> select * from mybatis.user where id =#{i} </select>
resultMap
元素是 MyBatis 中最重要最强大的元素- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- 通过设置ResultMap , 然后将 语句中的resultType 更改为resultMap 并让值为我们自己设置的id值
- 如果这个世界总是这么简单就好了。
6.日志
6.1 日志工厂
如果一个数据库操作出现了异常, 我们需要排除错误, 日志就是最好的帮手
- SLF4J |
- LOG4J | (掌握)
- LOG4J2 |
- JDK_LOGGING |
- COMMONS_LOGGING |
- STDOUT_LOGGING | (了解)
- NO_LOGGING
在Mybatis中, 具体使用哪一个日志 需要在设置中指定
<!--在核心配置文件中设置-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
运行语句输出结果
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 19717364.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@12cdcf4]
==> Preparing: select * from mybatis.user where id =?;
==> Parameters: 2(Integer)
<== Columns: id, name, pwd
<== Row: 2, xiangsing, 123456
<== Total: 1
{"id":2,"name":"xiangsing","password":"123456"}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@12cdcf4]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@12cdcf4]
Returned connection 19717364 to pool.
Process finished with exit code 0
6.2 LOG4J 日志
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
直接使用会报错,缺少依赖包
1、 先导入 LOG4J包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、创建log4j.properties ,编写配置文件
# priority :debug<info<warn<error
#you cannot specify every priority with different file for log4j
log4j.rootLogger=debug,stdout,info,debug,warn,error
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
#info log
log4j.logger.info=info
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.info.File=./src/com/hp/log/info.log
log4j.appender.info.Append=true
log4j.appender.info.Threshold=INFO
log4j.appender.info.layout=org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
#debug log
log4j.logger.debug=debug
log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.debug.File=./src/com/hp/log/debug.log
log4j.appender.debug.Append=true
log4j.appender.debug.Threshold=DEBUG
log4j.appender.debug.layout=org.apache.log4j.PatternLayout
log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
#warn log
log4j.logger.warn=warn
log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender
log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.warn.File=./src/com/hp/log/warn.log
log4j.appender.warn.Append=true
log4j.appender.warn.Threshold=WARN
log4j.appender.warn.layout=org.apache.log4j.PatternLayout
log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
#error
log4j.logger.error=error
log4j.appender.error = org.apache.log4j.DailyRollingFileAppender
log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.error.File = ./src/com/hp/log/error.log
log4j.appender.error.Append = true
log4j.appender.error.Threshold = ERROR
log4j.appender.error.layout = org.apache.log4j.PatternLayout
log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
输出
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.logging.LogFactory.setImplementation(LogFactory.java:135)Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.logging.LogFactory.setImplementation(LogFactory.java:135)Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.forceCloseAll(PooledDataSource.java:335)PooledDataSource forcefully closed/removed all connections.
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.forceCloseAll(PooledDataSource.java:335)PooledDataSource forcefully closed/removed all connections.
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.forceCloseAll(PooledDataSource.java:335)PooledDataSource forcefully closed/removed all connections.
[2020-08-03 21:35:21 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.forceCloseAll(PooledDataSource.java:335)PooledDataSource forcefully closed/removed all connections.
[2020-08-03 21:35:22 下午]:DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction.openConnection(JdbcTransaction.java:137)Opening JDBC Connection
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.popConnection(PooledDataSource.java:406)Created connection 838411509.
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction.setDesiredAutoCommit(JdbcTransaction.java:101)Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)==> Preparing: select * from mybatis.user where id =?;
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)==> Parameters: 2(Integer)
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:159)<== Total: 1
{"id":2,"name":"xiangsing","password":"123456"}
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction.resetAutoCommit(JdbcTransaction.java:123)Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction.close(JdbcTransaction.java:91)Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
[2020-08-03 21:35:23 下午]:DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource.pushConnection(PooledDataSource.java:363)Returned connection 838411509 to pool.
Process finished with exit code 0
简单使用
-
在要使用Log4j的类中, 导入包import org.apache.log4j.Logger;
-
使用
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void testLog4j(){
logger.info("进入testlog4j方法");
logger.debug("debug 中");
logger.error("出现错误");
}
7.分页
为什么分页?
- 减少数据的处理量
- 提供良好的用户体验
使用Limit分页
语法 : SELECT * from table limit start,end;
- limit num; 返回 num 个结果
- limit start,num; 从 第start个开始,返回num个结果
7.1使用Mybatis 实现分页
-
接口
public interface UserMapper { //根据id查询 User getUserById(int i); //分页查询 List<User> getUserByLimit(Map<String,Integer> map); }
-
mapper.xml
<select id="getUserByLimit" resultType="User" parameterType="map"> select * from mybatis.user limit #{start},#{end}; </select>
-
测试
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("start",1); map.put("end",2); List<User> userByLimit = mapper.getUserByLimit(map); for (User user : userByLimit) { System.out.println(user.toString()); } }
7.2 使用Roubounds 分页(了解, 效率低)
-
接口
public interface UserMapper { //分页查询 RowBounds实现 List<User> getUserByRowBounds(); }
-
mapper.xml
<select id="getUserByRowBounds" resultType="User"> select * from mybatis.user; </select>
-
测试
@Test public void getUserByRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds实现分页 RowBounds rowBounds = new RowBounds(1, 2); List<User> userList = sqlSession.selectList("com.qwrdxer.dao.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user.toString()); } }
7.3 分页插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SQ0p3izn-1596949630610)(C:\Users\19442\AppData\Roaming\Typora\typora-user-images\image-20200804192621244.png)]
8、 使用注解开发
8.1 面向接口编程
- 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
- 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的对系统设计人员来讲就不那么重要了
- 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约東)与实现(名实分离的原则)的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类
- 第一类是对一个个体的抽象,它可对应为一个抽象体( abstract class)
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面( interface);
- 个体有可能有多个抽象面。抽象体与抽象面是有区别的
三个面向的区别
- 面向对象是指,我们考康问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的
8.2使用注解开发
-
编写接口, 使用注解
public interface UserMapper { @Select("select * from mybatis.user") List<User> getUser(); }
-
在核心配置文件中绑定接口
<mappers> <mapper class="com.qwrdxer.dao.UserMapper"></mapper> </mappers>
-
测试
public class UserDaoTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要应用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> user = mapper.getUser();
for (User user1 : user) {
System.out.println(user1.toString());
}
sqlSession.close();
}
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
本质: 反射机制实现
底层: 动态代理
Mybatis 详细执行流程
8.3、CRUD
通过在工具类指定参数为true 设置自动提交事务
//条件查询
@Select("select * from user where id=#{id}")
//方法存在多个参数, 所有的参数前面必须加上@Param
User getUserByID(@Param("id") int id,@Param("name") String name);
//插入
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})")
int addUser(User user);
//修改
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
//删除
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid") int id);
关于@Param()注解
- 基本类型的参数或者String 需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略,但是建议加上
#{} ${}区别
#预编译, 能很大程度上防止sql注入
9.Lombok
使用步骤:
-
安装插件
-
在maven中添加lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
-
常用注解
@Getter and @Setter √ @FieldNameConstants @ToString √ @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data √ @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows
-
在原先的实体类中测试
package com.qwrdxer.pojo; import lombok.Data; @Data public class User { private int id; private String name; private String pwd; }
-
可以发现自动添加了常用的getset tostring 等方法
说明
@AllArgsConstructor 有参构造 @NoArgsConstructor 无参构造 ...
10、复杂查询: 多对一处理
- 如多个学生对应一个老师
- 对于学生这边而言, [关联] 多个学生关联一个老师 [多对一]
- 对于老师而言 , [集合] 一个老师对应多个学生 [一对多]
创建数据表
create table `teacher`(
`id` int(10) not null,
`name` varchar(30) DEFAULT null,
primary key (`id`)
) engine=INNODB DEFAULT charset=utf8
insert into teacher(`id`,`name`) VALUES (1,"秦老师");
create table `student`(
`id` int(10) not null,
`name` varchar(30) DEFAULT null,
`tid` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) engine=INNODB DEFAULT charset=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
实体类
--------------老师
package com.qwrdxer.pojo;
import lombok.Data;
@Data
public class Teacher {
private int id ;
private String name;
}
--------------学生
package com.qwrdxer.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
XML
<?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.qwrdxer.dao.StudentMapper">
</mapper>
报错: 在资源目录使用/而不是. resource com/qwrdxer/dao/*Mapper.xml
查询学生表以及对应的老师的信息
方式1.按照查询嵌套处理
思路:
- 查询所有的学生信息
- 根据查询出来的学生tid , 寻找对应的老师
<select id="getStudent" resultType="com.qwrdxer.pojo.Student">
select * from student;
</select>
简单的查询返回值为null
Student(id=1, name=小明, teacher=null)
Student(id=2, name=小红, teacher=null)
Student(id=3, name=小张, teacher=null)
Student(id=4, name=小李, teacher=null)
Student(id=5, name=小王, teacher=null)
<?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.qwrdxer.dao.StudentMapper">
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id= #{id}
</select>
</mapper>
-
对于学生实体类中特殊的成员Teacher 需要设置resultMap, 在其中使用association 来绑定实体类, 而它的值的获得是通过设置select 方法, 指定另一个查询来获得。
-
复杂的属性, 需要单独处理 :对象使用association 集合使用collection
javaType= 指定属性的类型
集合中的泛型信息, 使用ofType来获取
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"></result>
<result property="id" column="tid"></result>
</association>
</resultMap>
回顾MySQL多表查询
- 子查询( select * from xx where ss=(select * from xxxx ))
- 联表查询(select x.a,x.b,s.c,s.e from x,s)
11、一对多处理
比如: 一个老师有多个学生, 对于老师来说, 就是一对多的关系
实体类
-----学生类
package com.qwrdxer.pojo;
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
private int id ;
private String name;
//一个老师对应多个学生
private List<Student> studentlist;
}
-----老师类
package com.qwrdxer.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private int tid;
}
package com.qwrdxer.dao;
import com.qwrdxer.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface TeacherMapper {
//获取老师
@Select("select * from teacher")
List<Teacher> getTeacher();
//获取老师和对应学生的信息
List<Teacher> getTeacherAndStudent(@Param("tid") int id);
}
<mapper namespace="com.qwrdxer.dao.TeacherMapper">
<select id="getTeacherAndStudent" resultMap="TeacherStudent">
select s.id sid,s.name sname, t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{tid};
</select>
<!-- private int id ;
private String name;
//一个老师对应多个学生
private List<Student> studentlist;-->
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"></result>
<result property="id" column="tid"></result>
<collection property="studentlist" ofType="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
</collection>
</resultMap>
</mapper>
复杂的属性, 需要单独处理 :对象使用association 集合使用collection
javaType= 指定属性的类型
集合中的泛型信息, 使用ofType来获取
另一种方式
<!--另一种方式-->
<select id="getTeacherAndStudent2" resultMap="TeacherStudent2">
select * from teacher where id=#{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="studentlist" column="id" javaType="ArrayList" ofType="Student" select="selectStudent">
</collection>
</resultMap>
<select id="selectStudent" resultType="Student">
select * from student where tid=#{id};
</select>
小结
-
关联- association [ 多对一]
-
集合- collection [一对多]
-
JavaType | ofType
- javaType 指定实体类中属性的类型
- ofType 指定映射到List或者集合中的pojo类型, 泛型中的约束类型
注意点:
- 保证SQL的可读性
- 注意一对多和多对一中,属性名和字段名的问题
- 使用日志排错
面试高频
- mysql引擎
- innoDB 底层原理
- 索引
- 索引优化
12. 动态SQL
什么是动态SQL: 动态SQl 就是根据不同的条件生成不同的SQL语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0WHGBOtz-1596949630643)(C:\Users\19442\AppData\Roaming\Typora\typora-user-images\image-20200807215332473.png)]
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
环境搭建:
创建数据库
import com.qwrdxer.mapper.BlogMapper;
import com.qwrdxer.pojo.Blog;
import com.qwrdxer.utils.IDutils;
import com.qwrdxer.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
public class MyTest {
@Test
public void addInitBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog=new Blog();
blog.setId(IDutils.getId());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(2222);
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("JAVA如此简单");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);
blog.setId(IDutils.getId());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);
sqlSession.close();
}
}
----------------------------------
package com.qwrdxer.utils;
import org.junit.jupiter.api.Test;
import java.util.UUID;
public class IDutils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
@Test
public void test(){
System.out.println(getId());
}
}
-
导包
-
配置文件
-
util包
-
编写实体类
@Data public class Blog { private String id; private String title; private String author; private Date createTime;//属性名和字段名不一致 核心配置类中设置set <setting name="mapUnderscoreToCamelCase" value="true"/> private int views; }
-
mapper
IF语句
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</where>
</select>
@Test
public void queryBlogIF(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map =new HashMap();
map.put("author","root");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
sqlSession.close();
}
choose (when, otherwise)
MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。 从上 往下依次查看每个when标签的值, 如果为真, 拼接sql, 结束, 如果when都不为真, 拼接otherwise
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>
<when test="author!=null">
author=#{author}
</when>
<otherwise></otherwise>
</choose>
</where>
</select>
@Test
public void queryBlogChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map =new HashMap();
map.put("author","root");
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
sqlSession.close();
}
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>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
13缓存
13.1简介
查询 每次都需要连接数据库, 消耗资源, 如果将查询的结果暂存到可以直接取到的地方 --> 内存 : 缓存
再次查询到相同的数据是, 直接走缓存, 而不是数据库
- 什么是缓存[Cache]?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中, 用户查询相同的数据就不用数据库从磁盘中查询, 直接从缓存中获取, 从而提高查询效率, 解决了高并发系统的性能问题
- 为什么使用缓存?
- 减少和数据库的交互次数, 减少系统开销, 提高系统效率.
- 什么样的数据能够使用缓存?
- 经常查询并且不经常改变的数据
13.2 Mybatis缓存
-
MyBatis 包含一个非常强大的查询缓存特性, 它可以非常方便地制定和配置缓存.缓存可以 极大的提升查询效率。
-
Mybatis系统中默认定义了两级缓存, 一级缓存和二级缓存。
- 默认情况下, 只有一级缓存开启, (SQLSession级别缓存, 也称为本地缓存)
- 二级缓存需要手动配置, 它是namespace级别的缓存(Mapper)
- 为了提高扩展性, Mybatis定义了缓存接口Cache , 我们可以通过实现Cache接口来自定义二级缓存。
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
13.3 一级缓存
- 一级缓存也叫本地缓存: SQLSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据 ,直接从缓存中拿,没必要再去查询数据库
测试步骤:
- 开启日志
- 测试在一个Session中查询两次相同记录
- 查看日志输出
@Test
public void getUserByid(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userByID = mapper.getUserByID(1);
System.out.println(userByID.toString());
System.out.println("-----------查询重复数据-----------");
User userByID2 = mapper.getUserByID(1);
System.out.println(userByID2.toString());
sqlSession.close();
}
输出
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 141289226.
==> Preparing: select * from user where id =?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 小明, 12345678
<== Total: 1
User(id=1, name=小明, pwd=12345678)
-----------查询重复数据-----------
User(id=1, name=小明, pwd=12345678) <----< 直接从缓存中获取数据
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@86be70a]
Returned connection 141289226 to pool.
通过调用SQLSession.clearCache() 方法可以手动清理缓存
小节: 一级缓存是默认开启的, 只在一次SQLSession 有效
13.4 二级缓存
默认情况下,只启用了本地的会话缓存(一级缓存),它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件(Mapper) 中添加一行:
<cache/>
- 二级缓存也叫全局缓存, 一级缓存作用域过低,所以诞生了二级缓存
- 基于namespace 级别的缓存,一个名称空间(Mapper) 对应一个二级缓存
- 工作机制
- 一个会话查询一条数据, 这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭流, 和这个会话对应的一级缓存就没了, 但是我们想要的是, 会话关闭了,一级缓存的数据可以保存到二级缓存中
- 新的会话查询相同的信息时 ,就可以从二级缓存中获取内容
步骤:
-
开启全局缓存
在settings 中显示设置(默认开启)<setting name="cacheEnabled" value="true"></setting>
-
在要使用二级缓存的mapper 中开启(设置cache标签)
<cache/>
-
测试
需要将实体类序列化@Test public void getUserByid(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userByID = mapper.getUserByID(1); System.out.println(userByID.toString()); sqlSession.close(); System.out.println("第二个sqlsession 开启"); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User userByID2 = mapper2.getUserByID(1); System.out.println(userByID2.toString()); sqlSession2.close(); } Created connection 963522361. ==> Preparing: select * from user where id =? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, 小明, 12345678 <== Total: 1 User(id=1, name=小明, pwd=12345678) Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@396e2f39] Returned connection 963522361 to pool. 第二个sqlsession 开启 Cache Hit Ratio [com.qwrdxer.dao.UserMapper]: 0.5 User(id=1, name=小明, pwd=12345678)
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
查询语句可以显示的设置是否使用缓存
<select id="test" useCache="false"></select>
小结:
- 只要开启了二级缓存, 在同一个Mapper下有效
- 所有的数据都会优先放在一级缓存中
- 一个sqlsession关闭后, 才会将数据提交到二级缓存
13.5 缓存原理
13.6 自定义缓存- ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。
-
导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
-
自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
-
创建配置文件ehcache,xml 定义缓存策略
SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User userByID2 = mapper2.getUserByID(1); System.out.println(userByID2.toString()); sqlSession2.close(); }
Created connection 963522361.
> Preparing: select * from user where id =?
> Parameters: 1(Integer)
< Columns: id, name, pwd
< Row: 1, 小明, 12345678
<== Total: 1
User(id=1, name=小明, pwd=12345678)
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@396e2f39]
Returned connection 963522361 to pool.
第二个sqlsession 开启
Cache Hit Ratio [com.qwrdxer.dao.UserMapper]: 0.5
User(id=1, name=小明, pwd=12345678)
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
查询语句可以显示的设置是否使用缓存
<select id="test" useCache="false"></select>
小结:
- 只要开启了二级缓存, 在同一个Mapper下有效
- 所有的数据都会优先放在一级缓存中
- 一个sqlsession关闭后, 才会将数据提交到二级缓存
13.5 缓存原理
[外链图片转存中…(img-boi6uSeb-1596949630652)]
13.6 自定义缓存- ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。
-
导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
-
自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
-
创建配置文件ehcache,xml 定义缓存策略