文章目录
1、认识Mybatis
1.1 Mybatis简介
1、什么是mybatis??
- 1)Mybatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射;
- 2)几乎免除了所有的JDBC代码以及设置参数和获取结果集的操作;
- 3)可以通过简单的XML或注解来配置和映射原始类型、接口和Java类(POJO)为数据库中的记录
2、什么是ORM??
-
ORM(Object Relational Mapping)对象关系映射:为了解决面向对象与关系数据库存在的互补匹配的技术,即将Java程序中的对象以一种映射方式保存到数据库中。
-
对应关系如下所示:
面向对象概念 面向关系概念 类 表 对象 表的行(记录) 属性 列(字段) -
ORM框架的特点
- 把对数据库的操作封装成一套API;
- 具有操作数据库的CRUD操作;
- 具有独立性,当持久层发生改变时,不用修改任何业务代码
-
当前的ORM框架
- JPA 本身是一种ORM规范,由各大框架提供实现
- Hibernate
- Mybatis
3、为什么说mybatis是半自动化的ORM映射工具,它与全自动的有什么区别??
mybatis在查询关联对象或关联集合对象时,需要手动编写sql来实现。
4、传统的JDBC开发存在的问题以及Mybatis中的解决方法
- ① 频繁创建数据库连接对象、释放,容易造成系统资源的浪费,影响系统性能。 ==> 使用连接池解决
- 在mybatis-config中配置数据库连接池,使用数据库连接池管理连接对象
- ② sql语句定义、参数设置、结果集处理存在硬编码(写死的参数、sql语句)。
- 将sql语句配置在XxxMapper.xml配置文件中,与Java代码分离。
- ③ 结果集处理存在重复代码,处理麻烦。映射成Java对象比较方便
- Mybatis自动将Java对象映射成sql语句
- ④ 使用preparedStatement向占有位符号传参数时存在硬编码,因为sql语句的where条件不一样,可能多也可能少,修改sql还要修改代码,系统不易维护。
- mybatis自动将sql语句执行结果转换为Java对象。
1.2 持久化
1、持久化是程序数据在持久状态和瞬时状态转换的机制
- ① 即把数据保存到可永久存储的存储设备中;
- ② 主要应用:将对象保存在
- 数据库
- 磁盘文件
- xml数据文件
- ③ 机制:JDBC、文件IO
2、为什么需要持久化??
- 内存断电即失;
- 内存价格昂贵
1.3 JDBC介绍
1.3.1 简介
1、概念
Java DataBase Connectivity:Java数据库连接
2、本质
定义了操作所有关系型数据库的规则(接口),每个数据库厂商需要去实现这些接口,提供数据库驱动Jar包。我们可以使用这些接口编程,真正执行的代码是驱动JAR包中的实现类。
3、步骤
- 导入驱动jar包
- 编写代码注册驱动
- 获取数据库连接对象Connection
- 定义Sql语句
- 获取执行Sql语句的对象Statement
- 执行Sql语句,接收返回结果
- 处理结果
- 释放连接资源
1.3.2 解释需要使用的类
1、DriverManager驱动管理对象。
功能:
- 1)注册驱动
- static void registerDriver(Driver driver):注册给定的驱动程序。
- mysql5 之后可以不用写注册驱动
- 2)获取数据库连接
- static Connection getConnect(String url, String user, String password)
- url:jdbc:mysql://ip:端口号/数据库名称
- user:用户名
- password:用户密码
2、Connection接口:数据库管理对象
- 1)获取执行SQL的方法
- Statement createStatemnet()
- PreparedStatemrnt preparedStatement(String sql)
- 2)管理事务
- 开启:void setAutoCommit(boolean autoCommit):false为开启自动提交
- 提交:void commit()
- 回滚:void rollback()
3、Statement接口:用于执行静态SQL(参数是给定的)
-
boolean execute(String sql):任何SQL语句
-
int executeUpdate(String sql):执行DML语句
-
DML:insert、update、delete
-
返回值:影响的行数。可以通过返回值判断是否成功
- 大于0:成功
- 否则失败
-
4、ResultSet:结果集对象
功能:封装查询结果集
ResultSet executeQuery(String sql) throws SQLException; //执行DQL语句
方法:
- boolean next():游标(索引)向下移动一行
- getXxx(Param par):获取数据,一次只能获取某一行的某一列
- 参数:
- int 代表列表的编号,从1开始。 getString(1)获取第一列
- String 代表列名称 getString(“name”):获取name这一列
- 参数:
遍历的步骤:
- 游标向下移动一行,游标初始为-1
- 判断是否有数据
- 获取数据
5、PreparedStatement:执行sql
功能:使用该对象完成CRUD操作
1)sql注入问题:在拼接sql时,有一些sql关键字参与拼接,容易造成安全问题
2)解决sql注入问题:使用预编译,在写sql语句时参数使用”?“占位符,执行sql时,给"?"注入值
- 预编译: SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
select * from user where name=? and password=?
3)方法:prepareStatement(String sql)
4)给参数赋值,使用setXxx方法。
setName(1,"Mike");
//1:表示?的位置,从1开始
//Mike:?的值
setPassword(2,"123456");
2、第一个Mybatis程序
2.1 Mybatis的编程步骤
- 通过sqlSessionFactoryBuilder.build(inputStream)构建SqlSessionFactory
- 通过sqlSessionFactory.openSqlSession()方法获取sqlSession连接对象/实例
- 通过sqlSession执行数据库操作
- 调用sqlSession.commit()方法提交事务
- 调用sqlSession.close()方法关闭连接对象
2.2 程序步骤
-
搭建数据库并编写相应的数据库连接数据源db.properties
#连接数据库 #mysql 5的driver=com.mysql.jdbc.Driver #mysql 8以上的驱动器如下 driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8 #serverTimezone=UTC 时区 #useUnicode=true 使用Unicode编码 #characterEncoding=utf8 使用utf8编码 username=root password=xxxxxx
-
导入mybatis的jar包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4</version> </dependency>
-
编写mybatis的核心配置文件mybatis-config.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> <!-- 导入外部的数据库连接的配置文件 --> <properties resource="db.properties"/> <!-- 给声明的类起别名 --> <typeAliases> <typeAlias type="com.wei.pojo.User" alias="User"/> </typeAliases> <!-- 数据库环境 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <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> <!--transactionManager:事务管理器 JDBC MANAGED:让容器管理对象的声明周期 dataSource:数据源 unpooled:没有使用到“池的概念” pooled:利用“池”,复用数据库连接对象 JNDI:应用在EJB或应用服务器这类容器中 --> <mappers> <!--命名空间,类似包的概念,绑定一个对应的Dao|Mapper接口 --> <mapper resource="com/wei/mapper/UserMapper.xml"/> </mappers> </configuration>
-
编写mybatis工具类 MybatisUtils
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; public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; //使用静态代码块加载数据库资源并进行初始化,整个过程只会在第一次加载的时候初始化 static { try { /*根据核心配置文件获取 会话工厂 每一个数据库对应一个SqlSessionFactory实例,需要的连接对象由这个会话工厂产*/ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //build()方法不是静态方法,所以需要创建一个实例进行调用 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /*获取sqlSession对象 SqlSession:线程不安全的,类似Connection,提供操作数据库的CRUD方法*/ public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
-
创建实体类Pojo
-
编写mapper接口,实现CRUD操作
public interface UserMapper { /** 根据用户id获取用户信息 * @param id * @return 若存在,则返回用户详细信息 */ User getUserById(@Param("id")Integer id); //使用@Param注解给参数命名,在sql语句中直接通过 #{id}获取该参数信息 }
-
编写mapper.xml配置文件。注意namespace的编写,不要遗漏
<?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标准命名空间一定要写,不写就会报错 --> <mapper namespace="com.wei.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="User"> select id,username,password from user where id=#{id} </select> </mapper>
-
编写测试类
public void testGetUserById() { //获取连接对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println(mapper.getUserById(1)); sqlSession.close(); //关闭连接 }
2.3 添加日志文件
1、在mybatis.xml中添加日志配置
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
2、在pom.xml主配置文件中添加依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3、编写日志打印输出数据源
#日志文件由三部分组成
#root :设置默认的日志输出级别和风格。
#appender :可以把日志输出到控制台或文件中去。
#logger :设置自定义日志级别和风格。
#将等级为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/wei.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
控制台日志输出
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
//开启JDBC数据库连接池
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
//创造连接对象connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 282821294.
//sql语句的预编译
[com.wei.dao.UserMapper.getUserById]-==> Preparing: select id,username,password from user where id=?
//传入的参数
[com.wei.dao.UserMapper.getUserById]-==> Parameters: 1(Integer)
//获取的结果条数
[com.wei.dao.UserMapper.getUserById]-<== Total: 1
//结果详细信息
User(id=1, username=小华, password=xiaohua123)
//关闭数据库连接
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10db82ae]
//将连接对象返回给数据库连接池
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 282821294 to pool.
日志输出到文件
[DEBUG][21-01-06][org.apache.ibatis.logging.LogFactory]Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[DEBUG][21-01-06][org.apache.ibatis.logging.LogFactory]Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[DEBUG][21-01-06][org.apache.ibatis.datasource.pooled.PooledDataSource]PooledDataSource forcefully closed/removed all connections.
2.4 日志介绍
1、作用:
- 出错时可以根据输出的SQL语句快速排查问题;
- 将日志与代码隔离 <<==>> 日志框架的作用
2、Mybatis的内置日志工厂有:
- SLF4J
- Apache Commons Logging
- Log4j2
- Log4j
- JDK Logging
3、日志级别
ERROR > WARN > INFO > DEBUG > TRACE
- 例如设置日志级别为INFO后,只有优先级高于或等于INFO的日志信息才能被输出;
- 日志级别越低,输出的日志越详细。
4、日志的组成:
5、使用
-
在pom.xml中导入日志依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
在全局配置文件中配置
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
-
定义日志文件log.properties(如2.3中编写日志文件)
3、Mybatis原理
3.1 Mybatis核心组件
- SqlSessionFactoryBuilder构建器:创建SqlSessionFactory对象
- SqlSessionFactory会话工厂:创建SqlSession会话对象
- SqlSession会话对象:线程不安全的,类似connection对象。提供操作数据库的CRUD方法,可以调用操作方法环绕操作Mapper组件。
- Excutor执行器:SqlSession本身不能操作数据库,需要Excutor完成,其提供两个接口
- 缓存执行器(缺省)
- 基本执行器
- MappedStatement:映射语句封装执行语句时的信息,如SQL、输入参数、输出结果等
Mybatis有三种基本的执行器:
- SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭该对象
- ResueExecutor:重复使用Statement对象。执行update或select,以sql作为key查找Statement对象,如果存在就使用,不存在就新建一个Statement对象,用完后,不需要关闭该对象,而是将其存放在Map<String,Statement>中。
- BatchExecutor:批处理执行器。执行update(不执行select,JDBC批处理不支持select),将所有的sql都添加到批处理中(addBatch()),等待统一执行executeBatch(),它缓存了多个Statement对象,每个Statement对象都是addBatch()添加完毕后,等待逐一执行executeBatch()批处理。
【注】作用范围: Executor的这些特点,都严格限制在SqlSession生命周期范围内。
Mybatis中如何指定使用哪一种Executor执行器?
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
3.2 执行原理
- 1)读取 MyBatis 全局配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。Configuration类
- 2)加载映射文件Mapper.xml。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
- 3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
- 4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
- 5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
- 6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
- 7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
- 8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
4、Mapper映射器
目的:找sql语句
位置:mybatis-config.xml中的标签
方式:告诉Mybatis去哪里找映射文件
<!--1、使用相对路径的资源使用 **-->
<mapper resource="com/wei/dao/XxxMapper.xml"/>
<!--2、使用完全限定资源定位符(url):文件的路径 -->
<mapper url=".../XxxMapper.xml"/>
<!--3、使用映射器接口实现类的完全限定类名 -->
<mapper class="com.wei.dao.XxxMapper"/>
<!--4、将包的映射器接口全部注册为映射器 **-->
<mapper name="com.wei.dao"/>
#{ } 和 ${} 的区别???
<select id="getUserById" parameterType="int" resultType="User">
select id,username,password from user where id=#{id}
</select>
- #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理;
- Mybatis在处理#{}时,是以字符串为参数传入,将SQL中的#{}替换为"?",调用PreparedStatement的set方法来赋值;
- Mybatis在处理$${}时,是原值传入。就是把 ${}替换成变量的值,相当于JDBC中的Statement方法;
- 变量替换后,#{}对应的变量会自动加上单引号’ ',而${}对应的变量不会自动加上单引号;
- #{}可以有效的防止SQL注入,提高系统的安全性;${}不能防止SQL注入;
- #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
5、核心配置文件
5.1 属性properties
<!-- 导入数据库连接的配置文件 -->
<properties resource="db.properties"/>
若属性在不止一个地方进行了配置,那么按以下顺序进行加载:
- 首先读取在properties元素体内指定的属性;
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
5.2 设置setting
使用频率高的:
-
懒加载:lazyLoadingEnabled,默认情况下为false,表示不开启
<setting name="lazyLoadingEnabled" value="true">
-
日志实现:logImpl,指定mybatis所用日志的具体实现,未指定则自动查找
<setting name="logImpl" value="LOG4J">
-
缓存开启关闭:localCacheScope,默认为session,会缓存一个会话中执行的所有查询(一级缓存)
<setting name="localCacheScope" value="SESSION"/>
-
驼峰命名:mapUnderscoreToCameCase,是否开启驼峰命名。这属于自动映射,即从A_COLUMN到Java属性名中的 aColumn属性,默认不开启(false)。解决属性名和数据表中列名不一致的一种方法。
<setting name="mapUnderscoreToCamelCase" value="false"/>
5.3 类型别名typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!-- 起别名 -->
<typeAliases>
<typeAlias type="com.wei.pojo.User" alias="User"/>
</typeAliases>
<!-- 当这样配置的时候,User可以使用在com.wei.dao.User的任何地方 -->
<!--也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean -->
<typeAliases>
<package name="com.wei.dao"/>
</typeAliases>
【注】在注解的情况下,使用类的小写作为其别名
@Alias("user")
public class User{
}
6、ResultMap结果集映射
6.1 ResultMap
1、在编写XxxMapper中的sql语句可能出现的问题:字段名和属性名不一致
-
解决方法一:可以使用给sql语句中字段名起别名的方式(自动映射)
select password as passwd from user -- password 为字段名 -- passwd 为属性名 -- 自动映射的方式,mybatis会自动寻找实体类Pojo中的setXxx()方法,如果没有找到setPasswd()方法就会返回null
-
解决方法二:使用ResultMap集合
2、ResultMap设计思想:对简单的语句做到零配置,对关系复杂的语句,只需要描述它们之间的关系就行。
3、使用(手动映射):
<!-- 1.显示定义一个外部的ResultMap -->
<resultMap id="mapName" type="User">
<!--type=“全限定类名或别名” ,该属性表示把结果集中的每一行数据封装为什么类型的对象
id表示主键
property:实体类的属性名
column:字段名
result 非主键
-->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="passwd" column="password"/>
</resultMap>
<!--2. 在CRUD标签中引用外部的ResultMap -->
<select id="getUserById" resultMap="mapName" parameterType="int">
select id, username, password from user where id = #{id}
</select>
<!--resultMap:引用外部ResultMap
parameterType:需要传入的参数的类型
注意:sql语句后面不能加上分号 ";",不然会报错
-->
6.2 多对一处理
关系在多的一方,以多的一方为主导关联一的一方,使用association。针对单属性集合,通常直接使用内联查询;配置单一元素(非数组、集合)的关联对象。
@Data
public class Student {
private Integer id;
private String name;
private Integer tid;
private Teacher teacher; //多个学生可以对应一个老师,即多对一
}
@Data
public class Teacher {
private Integer id;
private String name;
}
属性:
- property:关联对象的属性名
- javaType:关联对象的属性类型
- select:发送额外的SQL
- column:指定列的值,传递给SQL语句
<mapper namespace="com.wei.dao.StudentMapper">
<!-- 需求:获取所有学生及其对应老师的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师ID->获取该老师的信息
3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
1. 做一个结果集映射:StudentTeacher
2. StudentTeacher结果集的类型为 Student
3. 学生中老师的属性为teacher,对应数据库中为tid。
多个 [1,...)学生关联一个老师=> 一对一,一对多
4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
-->
<select id="getStudents" resultMap="StudentAndTeacher">
select * from student;
</select>
<resultMap id="StudentAndTeacher" type="Student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select id,name from teacher where id= #{id};
</select>
<!-- 按照查询结果处理 *** -->
<select id="getStudents2" resultMap="StudentAndT">
select s.id sid,
s.name sname,
t.name tname
from student s, teacher t
where s.tid = t.id
</select>
<resultMap id="StudentAndT" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
6.3 一对多处理
- 关系在一的一方。
- 需要使用集合类型的关联属性,使用标签collection。
- 通常使用延迟加载,也就是额外的SQL处理。
延迟加载的原理:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentsByTeacher" />
<select id="getStudentsByTeacher" resultType="Student">
select * from student where tid=#{id}
</select>
<!--ofType:指定映射到List集合属性中POJO的类型
javaType:一个Java类的全限定名,或一个类型别名
column:数据库中的列名,或者是别名
select:用于加载复杂类型属性的映射语句的ID,它会从column属性中指定的列检索数据,作为参数传递给此select语句。
-->
7、分页
为什么需要分页??
对数据库操作最频繁的动作是查询,若查询大量数据时,用分页能够每次处理小部分数据,对数据库的压力就可以控制在可控的范围内。
实现方式
-
使用limit语句(在SQL层面)
- 语法:select * from table limit startIndex, pageSize
- 实例 select * from table limit 5, 10 //检索记录行6到15
- 检索从某个偏移量到记录集的结束的所有语句,可以指定第二个参数为 -1;
- select * from table limit n //检索前n个记录
- 公式:起始位置 = (当前页面 - 1)* 页面大小
-
RowBounds分页
-
在Java代码层实现
-
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。
-
在测试类中:
RowBounds rowBounds = new RowBounds((curPage - 1) * pageSize, pageSize);
-
通过session.xxx()方法传递rowBounds
//SqlSession接口 <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
-
8、注解开发
面向接口编程
1、根本原因:
- 解耦,可扩展,提高复用;
- 在分层开发中,上层不用管具体的实现,大家都遵守共同的约定 / 标准,使得开发变得容易,规范性更好。
2、思想:
系统设计的关键,各个对象之间的协作关系。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都要着重考虑。这也是系统设计的主要工作内容。
3、接口:是定义(规范、约束)与实现的分离(解耦)。
注解开发
1、Sql类型的主要分类(CRUD)
- @select:查询
- @update:更新
- @insert:插入
- @delete:删除
2、不再需要mapper.xml映射文件
3、使用:
-
① 在接口中添加注解
public interface UserMapper { @Select("select * from user where id=#{id}") User getUserById(@Param("id")Integer id); }
-
② 在mybatis的核心配置文件中注入
<mapper class="com.wei.dao.UserMapper" />
-
③ 测试
@Test public void getUserById() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(2); System.out.println(user); sqlSession.close(); } /*结果 User(id=2, username=小明, password=xiaoming123) */
4、本质:利用JVM的动态代理机制
9、动态SQL
1、指的是根据不同的查询条件,生成不同的SQL语句
2、包括四个关键字:
- if 条件判断
- choose(when,otherwise):类似switch,其中when类似case,otherwise类似default
- trim(where,set)
- foreach
3、SQL片段:当某个sql语句使用很多的时候,为了增加代码的重用性,简化代码,需要将这些代码抽取出来,然后直接使用。
-
提取
<sql id="if-title-author"> <if test="title != null"> title=#{title} </if> <if test="author!=null"> and author=#{author} </if>
-
使用
<select id="queryBlog" paramterType="map" resultType="map"> select * from blog <where> <include refid="if-test-author"></include> <!--还可以继续引用其他sql片段 --> </where> </select>
注
- 最好是基于单表定义sql片段,提高片段的可重用性;
- 在sql片段中不要包括where
4、foreach关键字的使用
<foreach collection="ids" item="id" open="and(" close=")" separator="or">
id=#{id}
</foreach>
- collection:指定输入对象中的集合属性
- item:每次遍历生成的对象
- open:开始遍历时的拼接字符串
- close:结束遍历时的拼接字符串
- separator:遍历对象之间需要拼接的字符串
10、缓存Cache
什么是缓存??
- 存在内存中的临时数据;
- 将用户经常查询的数据放在缓存中,不用再去磁盘获取
优势
- 提高查询效率,解决了高并发系统的性能问题;
- 减少和数据库的交互次数,减少系统开销。
Mybatis缓存
1、mybatis中存在两级缓存,分别是:
- 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,默认只开启一级缓存。SqlSession级别的缓存,也成为本地缓存,与数据库同一会话期间查询到的数据;
- 二级缓存: 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
2、Mybatis定义了缓存接口Cache,用于实现二级缓存。
3、一级缓存失效的四种情况
- ① sqlSession不同;
- ② sqlSession相同,但是查询条件不同;
- ③ sqlSession相同,但是两次相同条件的查询之间执行力增、删、改操作;
- ④ sqlSession相同,但是却手动清除了一级缓存 sqlSession.clearCache()
4、二级缓存
-
开启缓存
<setting name="CacheEnabled" value="true"/>
-
在每个mapper.xml中配置使用二级缓存
<cache eviction="FIFO" flushInterval="6000" size=512 readOnly="true"> <!--eviction:创建了一个FIFO缓存。(清除策略) flushIntervall:表示每隔多长时间刷新(单位:毫秒ms) size:可以存储结果对象或列表的512个引用 readOnly:返回的对象为只读 -->
-
清除策略。默认的缓存清除策略是LRU
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
交互次数,减少系统开销。
Mybatis缓存
1、mybatis中存在两级缓存,分别是:
- 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,默认只开启一级缓存。SqlSession级别的缓存,也成为本地缓存,与数据库同一会话期间查询到的数据;
- 二级缓存: 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
2、Mybatis定义了缓存接口Cache,用于实现二级缓存。
3、一级缓存失效的四种情况
- ① sqlSession不同;
- ② sqlSession相同,但是查询条件不同;
- ③ sqlSession相同,但是两次相同条件的查询之间执行力增、删、改操作;
- ④ sqlSession相同,但是却手动清除了一级缓存 sqlSession.clearCache()
4、二级缓存
-
开启缓存
<setting name="CacheEnabled" value="true"/>
-
在每个mapper.xml中配置使用二级缓存
<cache eviction="FIFO" flushInterval="6000" size=512 readOnly="true"> <!--eviction:创建了一个FIFO缓存。(清除策略) flushIntervall:表示每隔多长时间刷新(单位:毫秒ms) size:可以存储结果对象或列表的512个引用 readOnly:返回的对象为只读 -->
-
清除策略。默认的缓存清除策略是LRU
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
资料来源:
[1] 狂神说Java:https://www.bilibili.com/video/BV1NE411Q7Nx
[2] Mybatis官网:https://mybatis.org/mybatis-3/zh/index.html