文章目录
MyBatis
引言
现有JDBC代码的问题: 大量代码的冗余 1. 获得conn 2. 创建pstm 3. 绑定参数的方法 4. 发送参数,执行sql 5. 处理结果集 6. 处理异常
特点
介绍: 数据持久层框架(DAO将数据持久化到数据库),实现DAO层的代码。对JDBC代码的封装。
特点:
1. 封装通用功能,简化代码,提高开发效率(获得conn,绑定参数,发送sql,处理异常,处理结果集) 2. sql放在配置文件中,提高sql可维护性。 3. 自带连接池功能 4. 自带缓存(提高查询效率) [重点]
第一个MyBatis程序
核心编程思想
1. 书写DAO接口 2. 开发Mapper文件 SQL+绑定的参数===实现接口中的方法。
环境搭建
- 导入jar。
mybatis资料目录结构:
>
1. 导入mybatis的jar 2. 导入oracle的驱动jar 3. 导入mybatis依赖lib的jar。
- 引入配置文件:
1. mybatis-config.xml(连接数据库相关的参数) driverClassName 驱动类名 oracle.jdbc.OracleDriver url jdbc:oracle:thin:@localhost:1521:xe username hr password hr 连接池(POOLED)-------------配置连接池 2. XxxMapper.xml(相当于dao的实现类)
>
- 初始化配置
MyBatis实现DAO编码
需求: 使用MyBatis实现DAO的方式,添加一个person信息?
1. 写DAO接口 public interface PersonDAO{ void insert(Person person); } 2. 书写Mapper文件(DAO的实现类)[sql+参数] <insert id="实现接口的方法名" parameterType="参数中实体类型全类名"> insert into t_person values(seq_person.nextval,#{属性名},#{sex},#{age},#{mobile},#{address}) </insert> 属性: id: 实现的接口的方法名 parameterType: 参数类型(实体的全类名) sql语中绑定参数: #{属性名}
>```
- 注册管理mapper文件[在mybatis-config中配置]
<mappers> <!-- 注册管理所有的mapper文件 --> <mapper resource="com/baizhi/demo1/PersonDAOImpl.xml"></mapper> </mappers> resource: mapper文件相对于src的路径。
MyBatis使用的API[重点]
目的:
使用DAO:
1. 获得PersonDAO的对象。
2. 调用personDAO的方法。
常用的类:
sqlSession:
1. 获得dao接口的实现类的对象。
XxxDAO dao = sqlSession.getMapper(接口.class);
2. 相当于connection.[提交事务 关闭close,回滚事务]
SqlSessionFactory
1. 获得sqlSession
SqlSession session = sqlSessionFactory.openSession();
2. 保存封装mybatis-config.xml配置文件。
SqlSessionFactoryBuilder: 读取配置文件
Resources: 获得读取配置文件的输入流。
步骤:
1. 获得mybatis-config的输入流
2. 读取mybatis-config的文件,构造成SqlSessionFactory
3. 通过SqlSEssionFactory获得SqlSession
4. 通过SqlSession获得DAO接口的对象
5. 调用方法测试。
单表操作
修改
- 书写PersonDAO接口,声明修改的方法
public interfafce PersonDAO{ /** 参数: id: 条件 其他属性: 修改的新的值 */ public void update(Person person); }
- 在Mapper通过标签实现方法
<update id="update" parameterType="Person的全类名"> <!--sql,要绑定的参数--> update t_person set name = #{name},sex =#{sex},age=#{age},mobile=#{mobile},address=#{address} where id = #{id} </update>
- 注册mapper文件。[一个接口,对应一个mapper文件,注册一次]
删除
- 定义dao接口的方法
- 书写mapper文件标签
>3. 注册mapper文件。
限定参数绑定的名字
- 定义接口方法
public interface PersonDAO{ public void delete(@Param("指定参数绑定使用的名字")Integer id); }
- Mapper文件
<delete> delete from t_person where id = #{指定参数绑定使用的名字} </delete>
查询
查询单个
- 书写DAO接口方法
public interface PersonDAO{ Person selectById(Integer id); }
- mapper文件实现该方法(sql 参数 对查询结果映射实体对象)
核心: sql 参数
映射核心思想:
如果查询结果的列名和要封装的实体的属性名一样,将查询结果自动封装实体对象。
<select id="selectById" parameterType="java.lang.Integer" resultType="查询结果的一行数据映射的实体对象类型"> select id,name,sex,age,mobile,address from t_person where id = #{id} </select>
- 注册mapper文件
ResultType的作用
作用: 映射查询结果的列封装成实体的属性
要求: 查询结果的列名必须和实体的属性名一致
>
表列名和实体属性名不一致
通过sql 的as关键字,其别名方式,使查询结果的列名和实体的属性名一致。
查询多个
只需要明确单行数据映射的实体类型,
MyBatis会自动讲每个数据封装的每个实体放入list集合中。
- 书写DAO接口方法
public interface PersonDAO { List<Person> selectAll(); }
- 书写mapper文件中的标签
<select id="selectAll" resultType="rs的一行数据映射实体类型"> select id,name,sex,age,mobile,address from t_person </select>
- 注册mapper文件
多个参数(基本,包装,String)
方案一:
@Param给接口方法的参数起别名
public interface UserDAO { User selectByUsernameAndPassword(@Param("username")String username,@Param("password")String password); } select id,username,password from t_user where username = #{username} and password = #{password}
)
方案二:
>绑定参数通过#{参数的序号从0开始}
MyBatis的Mapper文件的sql书写 >或者 <
问题: 会发生转义
解决: 替换成转义字符
## MyBatis注意事项总结
1. 增删改必须要提交事务。(事务自动开启,手动提交模式)
2. 参数绑定情况
实体类型 #{属性名}
(单个参数)基本数据类型+包装类型+String #{随便}
没有参数
多个参数
3. mapper文件转义字符。
MyBatisUtil的封装
MyBatis核心API
SqlSession: 1. 相当于connection sqlSession.commit();//提交事务 sqlSession.rollback();//回滚事务 sqlSession.close();//将连接还回连接池。 2. 获得XXxDAO接口的对象。 轻量级对象,每次操作创建一个新的。 比如: Action SqlSession Connection SqlSessionFactory 作用: 封装mybatis-config配置文件的信息。 重量级对象,web应用只创建一个。对象的创建消耗资源。
package com.baizhi.demo4;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisUtil2 {
private static SqlSessionFactory factory = null;
private static ThreadLocal<SqlSession> tdl = new ThreadLocal<SqlSession>();
static{
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException("mybatis配置文件加载异常",e);
}finally{
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获得SqlSession
* @throws IOException
*/
public static SqlSession getSqlSession(){
//1. 获得DAO对象 sqlSession
SqlSession session = null;
//从当前线程中获得session
try {
session = tdl.get();
if(session == null){
//当前线程如果没有sqlSEssion
session = factory.openSession();
//将session存入当前线程
tdl.set(session);
}
} catch (Exception e) {
throw new RuntimeException("获得SqlSession的异常",e);
}
return session;
}
/**
* 根据接口的类对象,获得dao的对象。
* @param clazz
* @return
*/
public static <T> T getMapper(Class<T> clazz){
//获得sqlSession
SqlSession session = getSqlSession();
//调用session.getMapper(clazz);
T t = session.getMapper(clazz);
return t;
}
/**
* 提交事务+释放资源
*/
public static void commit(){
//sqlSession.commit();
try{
SqlSession session = getSqlSession();
session.commit();
}catch(RuntimeException e){
throw e;
}finally{
close();
}
}
/**
* 回滚事务+释放资源
*/
public static void rollback(){
try{
SqlSession session = getSqlSession();
session.rollback();
}catch(RuntimeException e){
throw e;
}finally{
//释放资源
close();
}
}
/**
* 释放session资源
*/
public static void close(){
//session.close();
SqlSession session = getSqlSession();
if(session != null){
session.close();
//从当前线程中移除该session
tdl.remove();
}
}
}
MyBatis使用技巧
配置文件提示
日志
MyBatis集成日志功能
mybatis运行期间的痕迹,通过控制台打印。
目的: 查看mybatis运行的痕迹,调试,验证。
使用
1. 导入日志的jar log4j.jar 2. 导入配置文件. log4j.properties [必须放在src根目录] 补救工作:
>
实体简化(别名)
简化实体的权限定名的书写
方案:
1. 给实体类的权限定名取别名。(mybatis-config配置文件) com.baizhi.demo1.Person 别名="Person" <typeAliases> <typeAlias type="实体的全类名" alia="别名"></typeAlias> <typeAlias></typeAlias> <typeAlias></typeAlias> </typeAliases> 2. Mapper使用实体类名 resultType="Person"
Mybatis参数绑定的底层原理[笔试]
1. 默认mapper文件绑定参数 #{xxx} ① 底层使用的PreparedStatement对象。 ② 使用的 SQL ? [预编译]sql执行。和参数绑定。 优点: 预编译, 防止sql注入。 预编译,相对效率高(一点) 缺点: 只能绑定数据值. sql关键字,列 非数据无法绑定。 2. 使用sql字符串拼接绑定参数 优点: 可以绑定任何内容。(关键词,列) 缺点: sql注入。 mybatis使用字符串拼接绑定参数: ${}
插入优化
JDBC的实现思路
MyBatis插入数据绑定id
<insert id='insert' parameterType="实体类型"> <!--在insert语句之前,执行select序列的sql,为了给参数的id属性绑定值。--> <selectKey order="BEFORE" resultType="java.lang.String" keyProperty="sql的执行结果绑定给参数的实体的哪个属性 id"> select seq_user.nextval from dual </selectKey> insert into t_user values(#{id},?,?) </insert>
查询关系映射
需求: 将查询结果映射成实体对象。[MyBatis自动完成]
方案1:
resultType: 作用: 将查询结果(而不是表)映射成实体对象。 要求: 查询结果的列名和实体属性名保持一致。 问题: 如果 表的列名和实体属性名不一致,通过as其别名的方式,使查询结果列名和属性名一致。
>
方案二:
给MyBatis定义映射关系 ResultMap: 明确告知mybatis 实体的属性名和查询结果的列名的对应关系。
Struts2+MyBatis整合
代码整合思路
编码思路梳理
需求: 登录功能?
搭建环境
导入strtus2相关的jar
导入mybatis相关的jar
数据库的驱动jar
注意: 处理jar包冲突
导入struts和myatis的配置文件
初始化配置(struts2核心控制器,mybatis-config的数据库连接参数)工具类导入: MyBatisUtil JdbcUtil 配置文件
功能开发步骤表
实体
DAO接口 XxxDAOImpl.xml(Mapper文件) 注册mapper文件
Service业务功能的接口 实现类
Action控制器
JSP页面
关联关系操作
一对多
实例需求: 员工(多)和部门(1)
(站在员工): 一个员工对应一个部门(1对1)
(站在部门): 一个部门对应多个员工(1对多)
表设计
核心思想:
- 表示表与表之间的关系。
- 1个员工对应1个部门,1个部门对应多个员工
设计1 和 n的表关系 原则 1. 将外键添加在n的一方。 --## 部门表 create table t_dept( id varchar2(36) primary key, name varchar2(50) ); --## 员工表 create table t_emp( id varchar2(36) primary key, name varchar2(50), age number(3), salary number(10,2), dept_id references t_dept(id) ); 重要: 添加数据,先添加没有外键的数据(部门信息),再添加存在外键的数据(员工信息)
实体设计
1. 在实体中添加关系属性,来表示实体之间的关系(对应表数据的关系) 2. 在N的一方添加1的一个关系属性。 3. 在1的一方添加N的一个List的关系属性
DAO(MyBatis如何查询两张表信息)
需求1:查询员工信息(工号,名字,年龄 , 薪资,所属部门的编号和名称)根据员工工号? DAO接口 //根据id查询员工信息 返回值Emp(员工信息+部门信息) //特点:查询员工,对应1个部门 Emp selectById(String id); Mapper文件 1. SQL select e.id,e.name,e.age,e.salary,d.id,d.name from t_emp e left join t_dept d on e.dept_id = d.id where e.id = '5'; 2. 参数 3. 将查询结果映射成一个实体对象
使用ResultMap映射1对1的关系。
特点: 如果关系属性是1----
<association></association>
需求2:
需求2:根据id查询部门信息,及其内部的所有员工信息? DAO接口方法 public Dept selectById(String id); Mapper文件中 1. SQL: select d.id,d.name,e.id as eid,e.name as ename,e.age as eage,e.salary as esalary from t_dept d left join t_emp e on d.id = e.dept_id where d.id = ?; 2. 参数绑定 3. 结果映射
ReusultMap映射集合关系属性
1对1关系
需求: 学生电脑管理系统
库表设计
表示1对1的关系
1. 添加外键(那张表添加都可以) ① 从业务的角度分析,后添加的数据对应的表。 ② 该表叫做副表(子表),添加外键。 2. 外键列约束 unique 唯一,不能重复
实体设计
互相保留彼此的一个关系属性
DAO设计
需求: 根据学生id查看学生信息(名字,年龄,手机号,使用的电脑编号,电脑名称)?
DAO接口设计
public interface StudentDAO{ //根据学生id查看学生信息(名字,年龄,手机号,使用的电脑编号,电脑名称)? public Student selectById(String id); }
Mapper文件
1. sql语句 2. 参数 3. 映射结果集(Student)
多对多
需求:
库表设计
设计原则:
n对n的关系建表 原则: 创建第三个关系表。 --学生表 create table t_student( id varchar2(36) primary key, name varchar2(50), age number(3), mobile varchar2(11) ); --课程表 create table t_course( id varchar2(36) primary key, name varchar2(50) ); --选课表 create table t_stu_course( sid references t_student(id), cid references t_course(id), primary key(sid,cid)-- 【联合sid和cid作为主键,非空 联合唯一】如果一个学生只能选择一个课程一次。 ); 注意: 如果产品需求中要求一个学生,选择一个课程,只能选择一次。 解决办法: 使选课关系表中,两个外键联合作为主键。
实体设计
设计原则: 互相保留对方的一个集合属性即可。
DAO设计
1. 根据id查询学生学号,姓名,年龄,手机号,课程编号,课程名称? 本质: 1对n DAO接口 public interface StudentDAO{ public Student selectById(String id); } Mapper文件 <resultMap> 主属性 id标签 一般属性 result标签 关系属性(list集合关系属性) collection标签。 </resultMap> <select> sql: 以学生表为主,关联关系表和课程表? </select>
MyBatis高级特性
动态sql
复用sql语句
案例: 复用sql的列名:
1. 定义sql片段 <sql id="Xxx_column">被复用sql片段</sql> 2. 引用sql片段 <include refid="Xxx_column"></include> 优点: 1. 简化sql书写。 2. 提高sql的可维护性。
(Where+if)动态sql简化查询
封装查询参数
package com.baizhi.demo2; /** * 所有可能的查询参数 * 学校 * 专业 * 状态 * 同时 * id * 查询对象: 封装了所有可能的查询参数 * @author Administrator * */ public class PersonQuery { private String id; private String school; private String professional; private Integer status; ....
DAO的简化
将页面所有可能的查询参数, 封装成XxxQuery对象。
Mapper的selectwhere条件设计where标签+if标签
核心: 调用dao方法传入的参数不同,决定了sql的条件不同 动态sql: 一个sql标签,由于传入参数不同,实际执行的sql语句也不同。
调用DAO:
1. 将查询条件放入XxxQuery对象中 2. dao.selectxxx(query);
#
update+if 动态修改
set标签: ① 替代set关键字 ② 自动忽略修改的列后面多余的 逗号。 if标签: test="dao方法的参数属性是否有值" 语法: <if test="参数的属性名 != null"> ...修改sql </if>
#### 动态sql删除
DAO接口的方法
void delete(@Param("ids")String[] ids);
Mapper文件
补充trim [了解]
作用: 1. 可以替代任何任何关键字(where set) 2. 可以忽略任何特殊字符(and ,)
缓存
现有数据查询的问题?
- 数据库的数据,来源于数据库的物理硬盘(150M/s~450M/s),速度慢?
- 数据获取,每次都要经过DB–>Tomcat的网络传输。,网络传输IO,降低速度?
解决思路
- 内存,读取速度(10000M/s),速度快于物理硬盘。
- 将数据临时存放在tomcat本地,再次获取数据,无需通过网络从DB传输给java。
缓存
概念: 数据的临时存放空间。 特点: 数据会被临时存放在内存中。(内存) 1. 可以将查询结果临时存放在缓存中。 2. 数据获取,从第二次开始,从缓存中获取。 3. 服务器内部缓存(JVM内部),每次获得数据,无需通过网络传输。 作用: 1. 提高查询效率 2. 降低的数据库的访问压力 数据库的硬件配置固定,大量查询,会侵占数据库的资源(CPU 内存) 数据库的可同时提供的连接数有限,大量查询,会侵占数据库的连接资源.
缓存核心架构:
MyBatis缓存
一级缓存
概念: SqlSession级别的缓存
核心思想:
特点:
- 每个sqlSession会有独立的缓存空间。
- SqlSession查询数据,默认放入缓存中。
- 第二次查询,sqlSession直接从缓存中获得数据,不会发送sql语句。
- 生命周期: 一个事务过程。【sqlSession.commit().一级缓存就会被清空】
应用
没什么用
二级缓存
概念: 1: 全局缓存 2: SqlSessionFactory级别缓存. 3: 多个SqlSession可以共享的。
工作机制:
1. 第一次查询,数据会从数据库(物理硬盘)获得数据,并且,通过网络传输给java。 2. 将第一次的查询结果,放入全局缓存[二级缓存]。 3. 第2+次以后,每次查询都从缓存获取。[从tomcat的jvm的内存中获得数据,避免从DB网络传输数据。]
二级缓存的使用步骤
- 二级缓存MyBatis默认自动开启了
如果mybatis版本,没有默认开启二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 被缓存的数据对象类型要可序列化
public class xxxx implements Serializable{
...
}
- 在使用二级缓存的查询sql所在的Mapper中添加
<mapper>
<cache></cache>
</mapper>
作用: 当前mapper文件下所有的select标签,查询结果都会默认自动放入到缓存中。
- 缓存的使用
查询工作机制:
1. 每次查询先从缓存中获得数据,如果获得,直接返回,不会发送sql,
2. 如果没有从缓存获得需要的数据,再发送sql,从数据库获得数据,会将查询结果放入缓存中。
深入二级缓存
特点 1. 开启了二级缓存的mapper文件中的select标签语句默认会使用缓存。(先从缓存中获取,获得到,返回,获得不到,从db获得,放入缓存) 先从select语句所对应的namespace的缓存空间中,获得数据,如果获得,返回,如果没有,则发送sql查询数据,并将查询结果放入sql所对应的namespace的缓存空间。 2. mapper文件中的 DML标签insert update delete ,会默认清空缓存。[为了保证缓存中数据一致性] 清空dml语句所在mapper文件,对应的namespace的缓存空间。 3. session.commit();当前session查询结果,才会放入二级缓存中。[空一级缓存]
缓存实现原理(源码)
- MyBatis二级缓存划分规则:
1. MyBatis二级缓存,是以(Mapper的)namespace进行划分 2. SQL标签 (select)操作的是sql所在的mapper文件的namespace对应的缓存空间。
- MyBatis缓存的源码实现?
每个namesapce对应的缓存源码实现?
1. mybaits的namesopace的缓存实现,本质HashMap 2. select语句的查询结果,是如果放入缓存 key: "namespaceid:执行的sql语句:实际传入的查询参数" value: 查询结果的返回值。
缓存的应用场景
原则:该数据的查询次数远远多于修改的次数
比如:
类别信息、登录的用户信息 适合放入缓存。 最新动态、新书、个性推荐.不适合放入缓存。
注意:
MyBatis二级缓存属于服务器内部缓存。 如果缓存数据过多,会挤占tomcat服务器内部(JVM)内部代码运行本身需要的内存空间,导致代码运行异常。
Ehcache缓存整合[重点]
简介
1. Ehcache 专职负责实现缓存。 2. Ehcache 分布式缓存。 3. Ehcache支持将数据序列化硬盘上(支持,不太可靠) 4. 服务器内部缓存(jvm进程内缓存)
Ehcache集成MyBatis(替换mybatis自带的缓存)
1. 导入Ehcache的jar,导入ehcache集成MyBatis的jar ehcache-core-2.6.5.jar mybatis-ehcache-1.0.2.jar 2. 在mapper中使用缓存是,指定使用Ehcache的缓存实现类 <cache type="Ehcache实现类的全类名"></cache>
配置文件[了解]
<!-- maxElementsInMemory:缓存最大的对象个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 maxElementsOnDisk:硬盘最大缓存个数。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。 数据淘汰策略。 默认策略是LRU (last resently used 最近最少使用)。 新闻(每天更新) 你可以设置为FIFO(first in first out先进先出) 或是LFU(least requently used 较少使用)。 --> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" maxElementsOnDisk="10000000" memoryStoreEvictionPolicy="LRU" />