JDBC编程有哪些不足之处,MyBatis是如何解决的?
-
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
- 解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
-
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。-
- 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
-
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
- 解决: Mybatis自动将java对象映射至sql语句。
-
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
- 解决:Mybatis自动将sql执行结果映射至java对象。
请说说MyBatis的工作原理
- 在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图
- 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
- 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
- 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
- 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
- Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
- MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
- 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
- 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
为什么需要预编译
-
定义:
- SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
-
为什么需要预编译
- JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
- 还有一个重要的原因,复制SQL注入
1.Mybatis中#{}与${}的区别(映射文件)
1. #{}:select * from emp where empno = ?
${}: select * from emp where empno = 7369
通过sql语句可以得出结论:
#{}是占位符,其处理方式是使用了参数预编译的方式,不会引起sql注入的问题,
调用PreparedStatement的set方法来赋值。
${}的处理方式是直接拼接sql语句,得到对应的sql语句,会有sql注入的危险
因此,我们推荐大家使用#{}的方式
但是要注意,${}也是有自己的使用场景的?
当需要传入动态的表名,列名的时候就需要使用${},就是最直接的拼接字符串的行为
${}: select * from emp where empno = 7369
通过sql语句可以得出结论:
#{}是占位符,其处理方式是使用了参数预编译的方式,不会引起sql注入的问题,
因此,我们推荐大家使用#{}的方式
但是要注意,${}也是有自己的使用场景的?
当需要传入动态的表名,列名的时候就需要使用${},就是最直接的拼接字符串的行为
2.当查询语句中包含多个参数的时候,我们应该如何获取需要的参数
1、如果是单个参数,
基本数据类型:那么可以使用#{}随便获取
引用数据类型:使用#{}获取值的是必须要使用对象的属性名
2、如果是多个参数:
我们在获取参数值的时候,就不能简单的通过#{}来获取了,只能通过arg0,arg1,param1,param2...这样的方式来获取参数的值
原因在于,mybatis在传入多个参数的时候,会讲这些参数的结果封装到map结构中,在map中key值就是(arg0,arg1,...)
(param1,param2...),这种方式非常不友好,没有办法根据属性名来获取具体的参数值
如果想要使用参数的话,可以进行如下的设置:
public List<Emp> selectEmpByEmpnoAndSal2(@Param("empno") Integer empno, @Param("sal") Double sal);这样的方式其实是根据@Param来进行参数的获取
当实体类中的属性名和表中的字段名不一样 ,怎么办
-
第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order"> select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; </select> 复制代码
-
第2种: 通过
<resultMap>
来映射字段名和实体类属性名的一一对应的关系。<select id="getOrder" parameterType="int" resultMap="orderResultMap"> select * from orders where order_id=#{id} </select> <resultMap type="com.jourwon.pojo.Order" id="orderResultMap"> <!–用id属性来映射主键字段–> <id property="id" column="order_id"> <!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–> <result property ="orderno" column ="order_no"/> <result property="price" column="order_price" /> </reslutMap>
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
-
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
-
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
mybatis中的缓存机制:
如果没有缓存,那么每次查询的时候都需要从数据库中加载数据,这回造成io的性能问题,所以,在很多情况下
如果连续执行两条相同的sql语句,可以直接从缓存中获取,如果获取不到,那么再去查询数据库,这意味着查询完成的结果
需要放到缓存中。
缓存分类:
1、一级缓存:表示sqlSession级别的缓存,每次查询的时候会开启一个会话,此会话相当于一次连接,关闭之后自动失效
2、二级缓存:全局范围内的缓存,sqlsession关闭之后才会生效
3、第三方缓存:继承第三方的组件,来充当缓存的作用
==================
一级缓存:表示将数据存储在sqlsession中,关闭之后自动失效,默认情况下是开启的
在同一个会话之内,如果执行了多个相同的sql语句,那么除了第一个之外,所有的数据都是从缓存中进行查询的
在某些情况下,一级缓存可能会失效?
1、在同一个方法中,可能会开启多个会话,此时需要注意,会话跟方法没有关系,不是一个方法就只能由一个会话,所以严格
记住,缓存的数据是保存在sqlsession中的
2、当传递对象的时候,如果对象中的属性值不同,也不会走缓存
3、在同一个连接,在多次查询过程中,如果修改了数据,那么缓存会失效
4、如果在一个会话过程中,手动清空了缓存,那么缓存也会失效
二级缓存:表示的是全局缓存,必须要等到sqlsession关闭之后才会生效
默认是不开启的,如果需要开启的话,需要进行如下设置
1、修改全局配置文件,在settings中添加配置
<setting name="cacheEnabled" value="true"/>
2、指定在哪个映射文件中使用缓存的配置
<cache></cache>
3、对应的java实体类必须要实现序列化的接口
在使用二级缓存的时候,可以包含多个属性值:
eviction:缓存淘汰机制:
LRU: 最近最少使用
FIFO:先进先出,按照添加缓存的顺序执行
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval:设置多长时间进行缓存刷新
size:引用的条数,是一个正整数,缓存中可以存储多少个对象,一般不设置,如果设置的话不要太大,会导致内存溢出
readonly:只读属性:
true:只读缓存,会给所有的调用的方法返回该对象的实例,不安全
false:读写缓存,只是返回缓存对象的拷贝,比较安全
一级缓存跟二级缓存有没有可能同时存在数据?
不会同时存在,因为二级缓存生效的时候,是在sqlsession关闭的时候
当查询数据的时候,我们是先查询一级缓存还是先查询二级缓存?
先查询二级缓存,然后再查询一级缓存