ORM(Object/Relation Mapping),中文名称:对象/关系 映射。把对象拆分到数据库列的过程封装起来,是一种解决数据库发展和面向对象编程语言发展不匹配问题而出现的技术。
(1)ORM技术的具体实现可以认为是一个整体。
(2)程序员在使用ORM时,只需要把对象交给ORM,不需要去编写拆卸对象的代码,而是由ORM把对象中属性值取出放入到SQL中。
(3)SQL执行结果后,如果执行的是查询,会由ORM把从数据库查询到的结果,转换为对象。程序员从ORM获取到的就是转换后的对象。
所以ORM技术相当于一个转换器,是面向对象语言和数据库之间的纽带。
常见ORM框架
(1)MyBatis:目前使用最多的持久层框架。半自动化ORM框架:需要程序与参与一部分,需要写SQL。
(2)Hibernate:零SQL的持久层框架,N年前使用最多的持久层框架。目前只能在一些老的项目中看到。标准ORM:只要给ORM对象,其他所有事情都不需要做。
(3)Spring Data JPA:目前个别公司中使用的持久层框架。是Spring Data家族中的一员。是对JPA(Java Persistence API,JDK5.0)的封装。
(4)Spring Data JDBC:Spring Data中的二级项目,类似Spring Data JPA,但框架的功能要比Spring Data JPA少一些。
(1)导入依赖
org.mybatis
mybatis
3.5.11
(2)写全局配置文件(JDBC里面4个核心参数,驱动类、连接字符串、用户名、密码)
(3)实体类(Entity):与数据库对应的java类
(4)写映射文件(mapper.xml)
insert into people values (default ,'张三',998)
(5)在全局配置文件中引入映射文件,记得要引入资源拷贝插件,不然编译后该文件将找不到。
(6)调用方式
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 Test {
public static void main(String[] args) throws IOException {
//1.实例化SqlSession对象,它封装了所有操作的接口
InputStream is = Resources.getResourceAsStream("mybatis.cfg.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
//2.执行SQL操作
int insert = sqlSession.insert("a.b.c.insert");
System.out.println(insert);
//3.如果是DML操作需要提交事务,如果是DQL不需要
sqlSession.commit();
//4.释放资源
sqlSession.close();
}
}
MyBatis框架内置日志工厂。日志工厂负责自动加载项目中配置的日志。MyBatis支持以下日志,当存在多个日志工具时,严格按照从上往下顺序使用,且只会使用一个。
SLF4J
Apache Commons Logging
Log4j 2
Log4j (deprecated since 3.5.9)
JDK logging
MyBatis结合Log4j实现日志
(1)添加Log4j依赖
log4j
log4j
1.2.17
(2)在Log4j配置文件log4j.properties中添加之下语句,可以实现只显示SQL执行的信息。
# log4j.logger是固定的,a.b.c是命名空间的名字可以只写一部分。
log4j.logger.a.b.c=TRACE
(3)在Mybatis的全局配置文件中指定日志类型
MyBatis结合Log4j2实现日志
(1)添加Log4j2依赖
org.apache.logging.log4j
log4j-core
2.19.0
(2)在配置文件 log4j2.xml中的Loggers标签中添加以下代码,可以实现只显示SQL执行的信息。
(3)在Mybatis的全局配置文件中指定日志类型
(1)软编码:把系统的一些核心配置信息,提出到一个单独的.properties文件中。
优点:项目分布到服务器后,依然可以修改
(2)硬编码:所有的后期可能发生的参数或配置,都写在.java源文件中
创建一个属性文件jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///ssm
jdbc.username=root
jdbc.password=123456
在mybatis.cfg.xml中引入属性文件
MyBatis中最常用的占位符为 #{},在MyBatis的mapper文件的SQL中使用#{}作为占位符。最终解析时会解析成JDBC的占位符?。
(1)单个简单参数:当参数为一个简答数据类型时。例如:八大基本数据类型、String类型等。可以通过#{任意内容}获取到。
(2)对象类型参数:可以使用#{对象中属性名}获取对象属性值。
(3)多个参数:可以创建一个对应类,通过对象进行传递。如果不想创建一个类,也可以把所有SQL参数放入到一个Map中。
(1)#{}被解析为?,用在设置列的值或条件值时,也可以使用在分页等需要设置具体值的情况。
(2)${}表示字符串拼接,用在动态设置表名和动态设置列名的情况下。
答:在MyBatis中${}和#{}都是在mapper.xml中获取方法参数的,用法是基本相同的
但是#{}最终会被解析为JDBC的占位符?,只能用在SQL中设置值的位置,无法在动态设置表名、列名、SQL命令的位置。
而${}在MyBatis解析时是字符串拼接。虽然能用在动态设置值的位置,但是因为可能出现SQL注入问题,所以很少使用在值的位置。多用在动态设置表名、列名、SQL命令的位置。
所以${}和#{}属于互补的关系,通过这两个表达式可以动态设置SQL中任意内容。
方法名 | 解释说明 |
---|---|
selectOne() | 查询一行数据时,返回值为Object。如果没有查询到,但是不能查询到多行。 |
selectMap() | 查询多行数据时,把其中某列结果当做key,每行结果为Value |
selectList() | 当查询多行数据时,返回值为List。如果没有查询到返回长度为零的List对象。 |
selectCursor() | 使用游标查询时使用,在大量数据时可以代替分页 |
select() | 万能方法,需要自己定义结果处理器 |
在mapper.xml文件中的DQL操作,必须写返回值的全限定路径,如果是集合类型,则写的是集合的泛型类型。
(1)selectOne():最多返回一行对应值,结果为:null、简单数据类型、对象。注意:如果SQL结果有多行,报TooManyResultsException(太多结果异常)。
(2)selectList() :方法主要用在多行查询时,查询时把每行结果按照resultType类型进行封装,最终放入到List中。
(3)selectMap(String statement,Object param,String key):比上面多了String key参数。
String key:指定查询结果中哪列的值作为map的key。map的value是整行数据,类型和resultType类型一致。
Java EE中最经典的是三层模型。包含表示层(Presentation)、业务逻辑层(Business Logic)、持久层(Persistence)
MVC 模型也是三层模型。包含模型层(Model)、视图层(View)、控制层(Controller)。
Java EE 三层模型和MVC模型最主要的区别是:
(1)Java EE三层模型中没有控制层,MVC中有控制层。
(2)Java EE三层模型中业务模型层是单独一部分,就是service层,MVC中模型层包含:业务模型(业务逻辑层)和数据模型(实体类,持久层)。
(3)Java EE三层模型中持久层就是dao层。MVC中虽然持久层在项目中是单独的包,但是在MVC概念中持久层属于数据模型中。
目前在软件开发过程中最常见的是五层模型:
(1)视图层。简单点说就是页面,可以是客户端页面技术,也可以是服务端页面技术。例如:HTML、JSP。
(2)控制层。处于业务层和视图层之间,根据业务层结果,控制视图层显示。例如:Servlet。
(3)实体层。就是实体类,负责封装数据,在各个层之间进行数据传递的载体。常见包名:domain、entity等。
(4)业务逻辑层。专门编写业务逻辑代码的一层。
(5)数据持久层/数据访问层。负责编写调用数据库的代码。具体技术:JDBC、MyBatis等。
(6)数据源层。一些持久化工具,负责存储数据的。例如:MySQL、Oracle等。
auto mapping:自动映射。当列名或列的别名与实体类属性名相同时不需要做额外配置
resultMap:手动定义映射关系。
camel case:驼峰命名规则
(1)Auto Mapping
自定映射方式就是前面所讲的方式,只要保证数据库中列名和属性名相同,就可以自动进行映射。
也可以给列起别名,让别名和属性名对应。对应时不用区分大小写。
(2)resultMap
当数据库列名和属性名不同时是无法进行自动映射的,这时需要手动指定映射关系。
(3) camel case
在MyBatis的全局配置文件中加入
注意:
1.指定的别名不区分大小写
2.
3.一个类可以有多个别名
4.起别名后,全限定类名依然可以使用
(1)给类明确指定别名
在mybatis.cfg.xml全局配置文件中,添加下面配置
(2)给指定包中所有的类指定别名
指定后所有类的别名就是类名。
(3)内置别名
别名 | 映射的类型 | 别名 | 映射的类型 | 别名 | 映射的类型 | ||
---|---|---|---|---|---|---|---|
_byte | byte | string | String | date | Date | ||
_long | long | byte | Byte | decimal | BigDecimal | ||
_short | short | long | Long | bigdecimal | BigDecimal | ||
_int | int | short | Short | object | Object | ||
_integer | int | int | Integer | map | Map | ||
_double | double | integer | Integer | hashmap | HashMap | ||
_float | float | double | Double | list | List | ||
_boolean | boolean | float | Float | arraylist | ArrayList | ||
boolean | Boolean | collection | Collection | ||||
iterator | Iterator |
接口绑定方式强制要求:
1.接口和mapper.xml必须在同一个包中
2.接口名必须和mapper.xml文件名相同
3.接口的全限定路径必须和mapper.xml中
4.接口的方法名必须和mapper.xml的id完全相同
实现步骤:
1.在mybatis.cfg.xml的
2.按照强制要求写接口和mapper.xml
3.使用:接口 对象=sqlSession.getMapper(接口.class);
对象.方法();
注解 | 解释 |
---|---|
@Select | 查询 |
@Insert | 新增 |
@Delete | 删除 |
@Update | 修改 |
@SelectKey | 主键回填 |
@SelectProvider | 调用SQL构建器。查询专用 |
@DeleteProvider | 调用SQL构建器。删除专用 |
@UpdateProvider | 调用SQL构建器。修改专用 |
@DeleteProvider | 调用SQL构建器。删除专用 |
@Param | 定义参数的名称 |
(1)SQL构建器:返回一个SQL语句的方法,有一个SQL类可以帮助我们构造SQL语句。
调用SQL构造器注解的使用:@SelectProvider( type = SQL构造器所在类的类对象,method = "方法名")
(2)使用注解设置结果映射
@Results(value = {
@Result(column = "peo_id",property = "id",id = true),
@Result(column = "peo_name",property = "name")
})
@Select("select * from tb_people where peo_name=#{name}")
Listselect2(People peo);
什么是动态SQL:在SQL中嵌入逻辑控制,根据不同的条件,形成不同的SQL语句
使用场景:多条件动态查询
(1)判断:
(2)逻辑与:and 逻辑或:or
(3)和switch类似功能:有break。
(4)
1.prefix:在trim字符串最前面添加的内容,自动会和之前的字符串之间有一个空格
2.suffix:在trim字符串最后面添加的内容,自动会和之前的字符串之间有一个空格
3.prefixOverrides:如果字符串最前面是以指定内容开头,去掉
4.suffixOverrides:如果字符串最后面是以指定内容结尾,去掉
注意:如果同时存在时,先删除,再添加。如果trim之间为空不会添加。
(5)
(6)
(7)
(8)
(9)循环:
1.collection="array | list" :如果循环的为数组则为array,如果为集合则为list
2.open="":最前面拼接的字符串
3.close="":最后面拼接的字符串
4.item="":迭代器
5.separator="":每次循环之间拼接的字符串
select max(p_id) from t_person
insert into people values(default,#{name},#{address})
insert into people values(default,#{name},#{address})
两种方式
N+1方式:
优点:SQL简单。支持延迟加载。
缺点:多做N次查询。
联合查询方式:
优点:一次查询。
缺点:SQL相对复杂。不支持延迟加载。
(1)N+1方式
(2)多表查询方式
只有在N+1查询方式中,是否立即加载关联属性。如果用到这个属性时再加载就是延迟加载。
使用方式:
(1)整个项目都启用延迟加载,在MyBatis全局配置文件中修改
(2)某个关联属性启用延迟加载,在
(1)缓存流程:
1.先找缓存
2.缓存返回结果,如果缓存中存在对应数据,执行结束
3.如果缓存中没有对应数据,访问数据库
4.数据库返回执行结果
5.把数据库查询结果放入到缓存中。
(2)一级缓存
默认MyBatis一级缓存就生效,缓存是一个Map对象,Key时 SqlSession + statement + sql,value是sql执行结果。生效范围:必须是用一个SqlSession对象,必须是同一个
(3)二级缓存
当SqlSession close或commit时,会把一级缓存内容刷新到二级缓存,二级缓存又叫SqlSessionFactory级缓存。生效范围:必须是同一个SqlSessionFactory对象,必须是同一个
开启方式:
1.全局开关:在mybatis.xml文件中的
2.分开关:在要开启二级缓存的mapper文件中开启缓存,使用
配置时,注解的查询无法缓存
属性 | 含义 | 默认值 |
---|---|---|
type | 自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口 | null |
readOnly | 是否只读true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全 | false |
eviction | 缓存策略LRU(默认) – 最近最少使用:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 | LRU |
flushInterval | 刷新间隔,毫秒为单位。默认为null,也就是没有刷新间隔,只有执行update、insert、delete语句才会刷新 | null |
size | 缓存对象个数 | 1024 |
blocking | 是否使用阻塞性缓存BlockingCachetrue:在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,保证只有一个线程到数据库中查找指定key对应的数据false:不使用阻塞性缓存,性能更好 | false |
3.二级缓存未必完全使用内存,有可能占用硬盘存储,缓存中存储的JavaBean对象必须实现序列化接口
public class Dept implements Serializable { }
MyBatis执行过程中涉及到非常重要的四个接口,这个四个接口成为MyBatis的四大核心接口:
* Executor执行器,执行器负责整个SQL执行过程的总体控制。默认SimpleExecutor执行器。
* StatementHandler语句处理器,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()。默认是PreparedStatementHandler。
* ParameterHandler参数处理器,参数处理器,负责PreparedStatement入参的具体设置。默认使用DefaultParameterHandler。
* ResultSetHandler结果集处理器,结果处理器负责将JDBC查询结果映射到java对象。默认使用DefaultResultSetHandler。
Interceptor拦截器,可以对四大核心接口进行拦截,拦截的效果和Java EE中的Filter有点类似,可以拦截前后做点事情。
(1)分页实体类
public class MyPageHelper {
protected static Integer pageStart;// 分页起始行
protected static Integer pageSize;// 查询条数
public static void startPage(int pageStartArg,int pageSizeArg){
pageStart = pageStartArg;
pageSize = pageSizeArg;
}
}
(2)写具体干的事情
package com.bjsxt.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Properties;
// 必须有的注解
/*
@Intercepts 表示当前是一个拦截器。
@Signature 表示签名。
type:拦截器主要拦截的类型.可以是四大核心接口。
method:拦截type中的哪个方法
args:method对应方法的参数。这个很重要,因为Java支持方法重载,不设置参数可能无法精确到具体的方法。
*/
@Intercepts(value = {@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class,Integer.class}
)})
public class MyPageHelperInterceptor implements Interceptor {
// 这个方法的作用:实现拦截业务
// 对于自定义分页插件来说,这个方法的作用就是在后面拼接limit x,y
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取拦截的对象
StatementHandler target = (StatementHandler) invocation.getTarget();
// 获取SQL绑定器
BoundSql boundSql = target.getBoundSql();
// 获取SQL语句
String sql = boundSql.getSql();
// 判断是否已经设置了分页条件
if(MyPageHelper.pageStart!=null&&MyPageHelper.pageSize!=null) {
// 注意limit前面空格
sql += " limit " +MyPageHelper.pageStart+","+MyPageHelper.pageSize;
}
// 把修改后的SQL重新放回去
MetaObject metaObject = SystemMetaObject.forObject(target);
// 第一个参数为固定值,表示绑定的SQL
metaObject.setValue("parameterHandler.boundSql.sql",sql);
// 放行继续执行
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// System.out.println(target.getClass().getName()); 通过输出可以查询执行此方法时目标对象
// 每次调用四大核心接口都会调用此方法,只需要对StatementHandler进行处理
if(target instanceof StatementHandler){
return Plugin.wrap(target,this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
// 获取到后面配置插件时的属性,设定属性名为dialect(方言),这个属性是自定义的。
System.out.println(properties.getProperty("dialect"));
}
}
(3)配置Mybatis的全局配置文件
(4)编写测试类
public class Test {
public static void main(String[] args) throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis.cfg.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
EmpMapper empMapper = session.getMapper(EmpMapper.class);
// 设置分页条件代码必须放在调用SQL上面
MyPageHelper.startPage(0,2);
List list = empMapper.selectAllpage();
System.out.println(list);
session.close();
}
}
(1)导入pagehelper的依赖
com.github.pagehelper
pagehelper
5.3.2
(2)在MyBatis的全局配置文件中引入
(3)编写一个查询全部的方法
(4)测试类中调用方法之前设置分页条件
PageHelper.startPage(1, 2);
(5)调用查询全部方法后,作为PageInfo的构造方法参数
List
students = studentMapper.selectAll3();
PageInfostudentPageInfo = new PageInfo<>(students);
在MyBatis中执行器共分为三个类型:SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor是默认的执行器类型。每次执行query和update(DML)都会重新创建Statement对象。
ReuseExecutor只预编译一次。把Statement放入到Map中,后面复用Statement(JDBC)对象。
BatchExecutor。用在update(DML)操作中。所有SQL一次性提交。
修改执行器类型:factory.openSession(Executor.SIMPLE|REUSE|BATCH)
关键类:
1.XPathParser:解析XML产生Document
2.Configuration:存放配置文件的配置信息
3.XMLConfigurationBuilder:解析XML配置
4.DefaultSQLSessionFactory:SqlSessionFactory接口的实现类
5.Transaction:事务。每个SqlSession会有一个Transaction
首先加载全局配置文件为输入流,交给XPathParser解析器解析为Document文档对象,然后使用DOM解析Document文档对象,把解析结果存放在Configuration配置类中。
通过DefaultSqlSessionFactory实例化工厂,实例化时会在全局存储Configuration配置对象。
在通过工厂对象创建DefaultSqlSession对象,在创建过程中,会同时创建Transaction事务对象、Executor执行器对象。如果当前项目有Interceptor拦截器,创建执行器时会执行拦截器。
通过JDK提供的Proxy创建接口的动态代理对象。
可以通过接口的代理对象调用方法。在调用方法时MyBatis会根据方法的类型判断调用SqlSession的哪个方法。例如:selectList、selectOne、update、insert等。
确定好具体调用SqlSession的哪个方法后,会按照执行器类型执行MyBatis四大核心接口,执行时也会触发拦截器Interceptor。最终会返回SQL的执行结果。
执行完方法后需要提交事务,提交时清空缓存、清除存储的Statement对象。
最后关闭SqlSession对象,释放资源。