MyBatis
技术体系
-
单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one
-
分布式架构
一个项目,拆分称很多个模块,每个模块是一个工程。每个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以堪称是一个单一架构的应用。
SpringCloud离不开SpringBoot。每个微服务就是使用SpringBoot开发的。而各个微服务整体管理交给SpringCloud。
框架framework
-
生活角度:
框架就是毛坯房,半成品。
SSM就是免费的毛坯房,大浪淘沙,Java领域中最流行的框架。
框架=框(限制 统一了风格)+架(支撑 保证质量、提高开发速度)
-
技术角度:
框架=jar包(特定问题的固定解决方案)+配置文件(个性化,可变,比如数据库的四个连接参数,SQL语句,访问路径)
常用的基于Java EE的三大开源框架,已经从SSH、SSH2过度到了SSM:SpringMVC、Spring、MyBatis。
总之,框架是一个半成品,已经对基础的代码进行了封装并提供响应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。
什么是MyBatis
MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。
- MyBatis支持定制化SQL、存储过程以及高级映射
- MyBatis避免了几乎所有的JDBC代码和手动设置参数以及结果集解析操作
- MyBatis可以使用简单的XML或注解实现配置和原始映射;将接口和Java的POJO映射称数据库中的记录
- MyBatis是一个半自动的ORM框架。
什么是ORM
O:Object 对象
R:relational 关系
M:Mapping 映射
Java类----数据库表
Java类的成员变量---数据库表的字段
Java类的对象----数据库表的记录
MyBatis和Hibernate的不同
MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。完成同样功能的两条SQL语句的性能可能相差十几倍到几十倍,在高并发、快相应要求的互联网系统中,对性能的影响更明显。
总之,因为MyBatis具有封装少、映射多样化、支持存储过程、可以进行SQL语句优化等特点,符合互联网高并发、大数据、高性能高相应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架。而对于性能要求不高的,比如内部管理系统、ERP等可以使用Hibernate。
使用MyBatis实现一个“HelloWorld”
-
创建数据库表(物理建模)
CREATE DATABASE `mybatis-example`; USE `mybatis-example`; CREATE TABLE `t_emp`( emp_id INT AUTO_INCREMENT, emp_name CHAR(100), emp_salary DOUBLE(10,5), PRIMARY KEY(emp_id) ); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jack",300.33); select * from t_emp
-
创建maven项目和实体类(逻辑建模)
使用MyBatis是否必须是Web项目?
不是的。只要是可以使用JDBC的地方都可以使用MyBatis
使用Spring也不要求必须是Web项目
但是使用SpringMVC必须是Web项目,它对Servlet进行了封装。
@Data @AllArgsConstructor @NoArgsConstructor public class Employee { //t_emp private Integer empId;//emp_id private String empName;//emp_name private Double empSalary;//emp_salary }
-
添加依赖(pom.xml)
<dependencies> <!-- Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <!-- Mybatis核心 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.3</version> </dependency> </dependencies>
-
创建配置文件(唯一的全局配置文件)
习惯上命名为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> <!--开发环境 --> <environments default="development"> <!--某一开发环境 --> <environment id="development"> <!--事务管理器,取值JDBC,使用是JDBC的事务 --> <transactionManager type="JDBC"/> <!--数据源:POOLED 带连接池的,提高获取连接的速度 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <!-- 指定映射文件的位置:映射文件中进行ORM映射--> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> </mappers> </configuration>
-
创建映射文件(会有多个,主要内容就是SQL语句)
<?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代理时取值不任意--> <mapper namespace="a.b.c"> <!-- id:唯一 resultType:返回值类型。如果返回值是集合,此处要想集合元素的类型(泛型) List<Employee> --> <select id="findAll" resultType="com.atguigu.entity.Employee"> select * from t_emp </select> </mapper>
-
代码开发
public class TestEmployee { @Test public void testFindAll() throws IOException { //1.根据配置文件创建了SqlSessionFactory //SqlSessionFactory factory = new DefaultSqlSessionFactory(); InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); //System.out.println(factory); //2.使用SqlSessionFactory创建了SqlSession SqlSession sqlSession = factory.openSession(); //3.使用SqlSession对数据库进行操作并得到结果 //仔细的体会一下:忽略了结果集处理的细节,直接返回List List<Employee> list = sqlSession.selectList("a.b.c.findAll"); //4.处理结果 list.forEach((emp)-> System.out.println(emp)); //关闭资源 sqlSession.close(); //factory.close(); } }
-
运行测试
问题1:java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for a.b.c.findAll
原因1:写错了a.b.c.findAll namespace+id
原因2:在配置config文件中没有指定映射(mapper)文件的位置
<mappers> <!-- 指定映射文件的位置:映射文件中进行ORM映射--> <mapper resource="mappers/EmployeeMapper.xml"></mapper> </mappers>
问题2:结果和记录条数相同,但是都是null
原因:数据库表和实体类已经映射,但是数据库表的字段和实体类的成员变量没有映射,名称不同,还不能自动映射。
<select id="findAll" resultType="com.atguigu.entity.Employee"> select emp_id empId,emp_name empName,emp_salary empSalary from t_emp </select>
问题3:出现了JDK版本的不匹配。提示1.5和1.8不匹配
原因:Maven项目创建的默认JDK版本是1.5
解决方案1:修改idea的多个配置
File——project structure——project——查看project SDK和Project language level是否是8
File——Modules——选中当前模块——调整Language level
File——setting——build,execution,deployment——Compiler——Java Compiler——查看module的bytecode version
解决2:给Maven指定全局配置或者项目的局部配置
<properties> <!--项目字符编码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--源代码编译时JDK版本 --> <maven.compiler.source>8</maven.compiler.source> <!--运行时JDK版本 --> <maven.compiler.target>8</maven.compiler.target> </properties>
问题4:MyBatis简单在哪?
这是在搭建项目框架,很多操作只需要在此处做一次。搭建好框架后,其实的操作就是:
在映射文件中写sql语句和在测试类调用sql语句。
之前在JDBC中SQL语句写在代码中,现在SQL语句写在映射文件中,便于修改。
//仔细的体会一下:忽略了结果集处理的细节,直接返回List
List<Employee> list = sqlSession.selectList("a.b.c.findAll");
误区:SQL文件在xml文件,如果每次要执行该SQL语句时都去读取xml文件,效率太低。
解释:创建SqlSessionFactory时,会将配置文件和映射文件的信息都读到内存中,按照特定的结构来存在,不会每次都读硬盘。
"HelloWorld"的完善
1. 添加日志log4j
1)添加依赖
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2)准备配置文件,比如在resources目录下,名称固定log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
3)观察效果(出现了SQL语句的DEBUG日志)
4)理解配置文件
a. Appender:目的地 日志写到哪里 ConsoleAppender 控制台 org.apache.log4j.FileAppender 写到文件
b. Layout:格式 日志写出什么样子 PatternLayout 指定格式 SimpleLayout HTMLLayout
c. Level:日志级别 FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
log4j曾经是最流行的日志框架,但是已经不再维护;现在建议使用log4j2和logback。
2. 提取连接参数属性文件
1)提取属性文件。要写在resources目录下,名称任意
wechat.dev.driver=com.mysql.jdbc.Driver
wechat.dev.url=jdbc:mysql://localhost:3306/mybatis-example
wechat.dev.username=root
wechat.dev.password=root
2)在配置文件中引入并使用属性文件
<!--读取指定的属性文件 -->
<properties resource="jdbc.properties"></properties>
<!--开发环境 -->
<environments default="development">
<!--某一开发环境 -->
<environment id="development">
<!--事务管理器,取值JDBC,使用是JDBC的事务 -->
<transactionManager type="JDBC"/>
<!--数据源:POOLED 带连接池的,提高获取连接的速度 -->
<dataSource type="POOLED">
<property name="driver" value="${wechat.dev.driver}"/>
<property name="url" value="${wechat.dev.url}"/>
<property name="username" value="${wechat.dev.username}"/>
<property name="password" value="${wechat.dev.password}"/>
</dataSource>
</environment>
</environments>
MyBatis单表开发1-无接口方式
1. findById
<select id="findById" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>
@Test
public void testFindById() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
SqlSession sqlSession = factory.openSession();
//3.使用SqlSession对数据库进行操作并得到结果
//仔细的体会一下:忽略了结果集处理的细节,直接返回List
Employee emp = sqlSession.selectOne("a.b.c.findById",2);
//4.处理结果
System.out.println(emp);
//关闭资源
sqlSession.close();
}
注意事项:
- 映射文件使用#{}来接收参数,参数名任意,但是建议见名知义
- 测试类中调用selectOne(),需要指定具体的参数
2. save
<!--insert update delete 没有resultType属性。有返回值,是int,代表DML操作影响了几条记录 -->
<insert id="insertEmp">
insert into t_emp values(null,#{empName},#{empSalary})
</insert>
@Test
public void testInsertEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
Employee emp = new Employee(null,"John",400.0);
int n = sqlSession.insert("a.b.c.insertEmp",emp);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}
注意事项:DML操作需要提交事务
//方式1:自动提交事务,每个DML操作时一个单独的事务
SqlSession sqlSession = factory.openSession(true);
方式2:手动的提交事务
sqlSession.commit();
问题:There is no getter for property named 'empName1' in 'class com.atguigu.entity.Employee'
insert into t_emp values(null,#{empName},#{empSalary})
底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术
如果没有getter方法,就会使用使用反射直接操作同名的成员变量
3. update
<update id="updateEmp">
update t_emp set emp_name=#{empName},emp_salary=#{empSalary} where emp_id =#{empId}
</update>
@Test
public void testUpdateEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
Employee emp = new Employee(6,"Johnson",500.0);
int n = sqlSession.update("a.b.c.updateEmp",emp);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}
4. delete
<!-- <delete id="deleteEmp">-->
<!-- delete from t_emp where emp_id = #{abc}-->
<!-- </delete>-->
<insert id="deleteEmp">
delete from t_emp where emp_id = #{abc}
</insert>
@Test
public void testDeleteEmp() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
//方式1:自动提交事务,每个DML操作时一个单独的事务
//SqlSession sqlSession = factory.openSession(true);
//方式2:手动的提交事务
SqlSession sqlSession = factory.openSession();//默认false
//3.使用SqlSession对数据库进行操作并得到结果
//int n = sqlSession.delete("a.b.c.deleteEmp",6);
int n = sqlSession.update("a.b.c.deleteEmp",6);
//手动提交或者回滚事务
sqlSession.commit();
//sqlSession.rollback();
//4.处理结果
System.out.println(n);
//关闭资源
sqlSession.close();
}
5.细节问题
-
映射文件中使用delete、update、insert标签没有区别,代码中调用SqlSession的insert、update、delete方法没有区别,关键是SQL语句
-
因为不管调用insert、update、delete哪个方法,底层调用的都是update(),更底层都是调用JDBC的executeUpdate()
-
不管查询是调用的是selectList(),还是selectOne(),底层调用的都是selectList.
-
目前已经学习了SqlSession的哪些方法
(1) selectList
(2) selectOne
(3) insert
(4) update
(5) delete
(6) Commit
-
MyBatis的常用API
(1) SqlSessionFactory
(2) SqlSession: 它是Connection,还是Statement呢?都不是!!!!
MyBatis单表开发2-Mapper代理接口方式
前面已经使用MyBatis完成了对Emp表的CRUD操作,都是由SqlSession调用自身方法发送SQL命令并得到结果的,实现了MyBatis的入门。
但是却存在如下缺点:
1).不管是selectList()、selectOne()、selectMap(),都只能提供一个查询参数。如果要多个参数,需要封装到JavaBean中,并不一定永远是一个好办法。
2).返回值类型较为固定 。
3).只提供映射文件,没有提供数据库操作的接口,不利于后期的维护扩展,也和之前开发习惯不符。
在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式。在实际开发中也使用该方式。
Mybatis中的Mapper接口相当于以前的Dao。但是区别在于,Mapper仅仅是接口,我们不需要提供实现类。
1. 定义接口 相当于使用JDBC时的DAO接口,但是不需要写实现类
public interface EmployeeMapper {
public List<Employee> findAll();
public Employee findById(Integer empId);
public int saveEmp(Employee emp);
public int updateEmp(Employee emp);
public int deleteEmp(Integer empId);
public List<Employee> findEmp(String ename,String salary);
}
2. 映射文件
<!-- 命名空间,使用了Mapper代理后,namespace的取值,必须是Mapper接口的完整路径名称-->
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<!-- id:唯一,必须和Mapper接口的方法名一致 -->
<select id="findAll" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
</select>
<insert id="saveEmp">
insert into t_emp values(default,#{empName},#{empSalary})
</insert>
</mapper>
对命名空间namespace有要求:必须是Mapper接口的完整路径名称
对具体操作的ID有要求:必须是接口的方法名
异常:org.apache.ibatis.binding.BindingException: Type interface com.atguigu.mapper.EmployeeMapper is not known to the MapperRegistry.
异常:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.atguigu.mapper.EmployeeMapper.findAll
3. 测试
public class TestEmployee {
SqlSession sqlSession = null;
@Before
public void before() throws IOException {
//1.根据配置文件创建了SqlSessionFactory
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//2.使用SqlSessionFactory创建了SqlSession
sqlSession = factory.openSession();//默认false
}
@After
public void after(){
//手动提交或者回滚事务
sqlSession.commit();
//关闭资源
sqlSession.close();
}
@Test
public void testFindAll(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> list = mapper.findAll();
list.forEach((emp)-> System.out.println(emp));
}
@Test
public void testInsertEmp(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = new Employee(null,"bill gates",500.0);
int n = mapper.saveEmp(emp);
System.out.println(n);
}
}
注意1: 因为MyBatis的CRUD操作流程固定,含有共同的代码,可以提取,并添加@Before、@After
注意2:使用该方式,第一步要通过SqlSession的getMapper()获取指定接口的动态生成的实现类对象
底层原理:底层使用了动态代理的设计模式实现了MyBatis的Mapper代理(接口绑定)
给SQL语句传参的两种方式
1. 使用#{} 底层使用PreparedStatement
<select id="findById" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = #{empId}
</select>
2. 使用${} 底层使用Statement
<select id="findById2" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = ${empId}
</select>
3. 使用${}和#{}实现模糊查询
#{}
<select id="findEmp" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat("%",#{ename},"%")
</select>
${}
<select id="findEmp2" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like '%${ename}%'
</select>
结论:推荐使用#{},安全,不需要拼接字符串
数据输入
讲解的是方法的参数可以是一个还是多个,是基本类型还是复杂类型,在SQL语句中应该如何接收。肯定是使用#{}接收,但是具体如何来接收。
给实体类起别名,方便resultType使用
方法1:给每一个类起别名,别名任意
<typeAliases>
<typeAlias type="com.atguigu.entity.Employee" alias="employee"></typeAlias>
<typeAlias type="com.atguigu.entity.Dept" alias="dept"></typeAlias>
</typeAliases>
方法2:给一个包下的所有类起别名,别名是类名的首字母小写,其他字母不变
<typeAliases>
<package name="com.atguigu.entity"/>
</typeAliases>
1. 单个简单参数(Integer、Double、String)
public Employee findById(Integer empId);
public List<Employee> findEmp(String empName);
<select id="findById" resultType="employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = #{empId}
</select>
<select id="findEmp" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like "%"#{ename}"%"
</select>
使用#{}来接收,参数名任意,建议见名知义。 经过测试发现对于模糊查询,#{}也可以使用 "%"#{ename}"%",要求是双引号。
2. 单个引用参数(Employee)
public int saveEmp(Employee emp);
<insert id="saveEmp">
insert into t_emp values(default,#{empName},#{empSalary})
</insert>
底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术,如果没有getter方法,就会使用使用反射直接操作同名的成员变量
3. 单个引用参数(Map)
多个参数封装在map中传给mapper。
public List<Employee> findEmp2(Map map);
<select id="findEmp2" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat("%",#{ename11},"%") and emp_salary>=#{minSalary}
</select>
@Test
public void testFindEmp2(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map map = new HashMap<>();
map.put("ename11","oh");
map.put("minSalary",400);
List<Employee> list = mapper.findEmp2(map);
list.forEach((emp)-> System.out.println(emp));
}
#{key}使用Map的key来接收该key对应的value
4. 多个简单参数(Integer、Double、String)
public List<Employee> findEmp3(String ename,Double minSalary);
public List<Employee> findEmp4(@Param("ename") String ename,@Param("minSalary") Double minSalary);
<select id="findEmp3" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat("%",#{param1},"%") and emp_salary>=#{param2}
</select>
<select id="findEmp4" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like concat("%",#{ename},"%") and emp_salary>=#{minSalary}
</select>
如果没有使用@Param的话,参数只能是: parameters are [arg1, arg0, param1, param2]
推荐使用@Param方式,增加可读性。
5.多个引用参数(Employee,Dept)
public int insertEmployee(@Param("emp") Employee employee,@Param("dep") Department department);
<insert id="insertEmployee">
insert into t_emp(emp_id,emp_name,emp_salary,dep_id) values(null,#{emp.empName},#{emp.empSalary},#{dep.depId})
</insert>
数据输出
1.基本数据类型
DML 返回一个int值,表示影响的行数
dao
int selectEmpCount();
mapper
<select id="selectEmpCount" resultType="int">
select count(*) from t_emp
</select>
2.实体类型
返回值是一个实体类型,resultType就写该类的完整路径,也可以使用alias别名,别名在mybatis配置文件中设置。
dao
public Employee findById(Integer empId);
mapper
<select id="findEmp" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like "%"#{ename}"%"
</select>
3.List类型
返回值是一个List,resultType写List的泛型类型,也就是元素的类型 。
dao
public List<Employee> findAll();
mapper
<select id="findAll" resultType="com.atguigu.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
</select>
此处使用了sql中的别名,如果pojo类和表中字段名不完全相同,那么需要我们手动对应【也就是对返回值字段重新命名】。同样的,resultType是用来对应表名的。
4.Map类型
前提:返回值只有一行记录,可以有多个字段。它是使用字段名(别名)当作key,因此不能有多行数据,使用字段值作为value。
但是对于findById方法来说则完全没有必要使用Map,而是使用对应的实体类,更加合理。
dao
Map<String,Object> selectEmpNameAndMaxSalary();
mapper
<select id="selectEmpNameAndMaxSalary" resultType="map">
SELECT
emp_name 员工姓名,
emp_salary 员工工资,
(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
FROM t_emp WHERE emp_salary=(
SELECT MAX(emp_salary) FROM t_emp
)
</select>
5.返回自增主键
多用于插入数据。
public int saveEmp4(Employee emp);
情景1:表主键支持自增。
mapper
<insert id="saveEmp" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp values (null,#{empName},#{empSalary})
</insert>
测试类
@Test
public void testSaveEmp3(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = new Employee(null,"washington",700.0);
System.out.println(emp.getEmpId());
int n = mapper.saveEmp4(emp);
System.out.println(n);
System.out.println(emp.getEmpId());
}
情景2:不管字段支不支持自增,都可以使用
mapper
<insert id="saveEmp">
<selectKey order="AFTER" keyProperty="empId" resultType="int">
select @@identity
</selectKey>
insert into t_emp values (null,#{empName},#{empSalary})
</insert>
数据库表字段和实体属性的对应关系
1.自动映射
如果数据库字段和实体类一致,会直接自动映射。
2.手动映射——起别名
也就是使用sql对查询结果起别名。如果数据库字段和实体类不一致,就需要手动映射,可以给每个列起一个别名,别名就是实体类的属性名。
3.手动映射——驼峰命名
起别名方式的缺点:
每个select都要起别名。
条件:如果数据库的表字段和属性名不一致,但是有规律,比如字段是emp_id,而实体类属性是empId。那么就满足驼峰命名。
开启驼峰命名
在MyBatis-config.xml配置文件中配置。这个功能由mapUnderscoreToCamelCase控制。
<settings>
<!-- 具体配置 -->
<!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
在mybatis配置文件中,由于引入了dtd文档,所以元素是有顺序的,不能违反顺序。
4.手动映射——ResultMap
如果数据库表字段和实体类不一致,但是也没有规律,那么可以使用别名来解决。但是每个select都要写一份,非常麻烦。此时就可以使用ResultMap,它可以定义一次,到处使用。
比如:
<resultMap id="employeeMap" type="com.dyy.Employee">
<!--主键-->
<id column="emp_id" property="empId"/>
<!--非主键-->
<result column="emp_name" property="empName"/>
<result column="emp_salary" property="empSalary"/>
</resultMap>
多表查询
在实际开发中,经常会将来自多张表的数据在一个位置显示。比如查询并显示的员工信息中会有来自部门表、岗位表的数据,而后台一般是定义一个方法: List findUser(conditions); 这就要求User中要包含部门Dept、岗位Position的信息。
1.如何实现多表查询
- 连接查询,使用MyBatis映射配置自动完成数据的组装,只需要执行一条SQL语句
- 分步查询,使用MyBatis映射配置自动完成数据的组装,需要执行多条SQL语句才能达到目的
2.关联关系【表之间的关系】
-
数量关系
- 一对一
- 一对多
- 多对多
-
关联关系的方向
主要实现在Java实体类中
- 双向:双方都可以访问到对方
- 单向:双方中只有一方能够访问到对方
3.多表查询
step1.创建数据库表
CREATE TABLE `t_customer` (
`customer_id` INT NOT NULL AUTO_INCREMENT,
`customer_name` CHAR(100),
PRIMARY KEY (`customer_id`)
);
CREATE TABLE `t_order` (
`order_id` INT NOT NULL AUTO_INCREMENT,
`order_name` CHAR(100),
`customer_id` INT,
PRIMARY KEY (`order_id`)
);
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_customer` (`customer_name`) VALUES ('c02');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '2');
select * from t_customer
select * from t_order
step2.创建项目并创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private Integer customerId;
private String customerName;
//对多
private List<Order> orderList = new ArrayList<>();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;//知道所属客户的id,建议保留 insert
//private String customerName;
//对单
private Customer customer;//知道所属客户的所有信息 select
}
数据库之间通过外键建立关联关系。
两个实体类之间通过属性(成员变量)来建立关联关系。
step3.搭建项目框架
- 实体类
- Mapper接口
- Mapper映射文件
- 测试类
- mybatis-config.xml(mybatis配置文件,指定了映射文件的位置)
step4.(多、一)对一
查询订单信息(包含客户信息)
(1)Mapper接口
public interface OrderMapper {
/**
* 查询指定编号的订单,返回该订单信息(其中携带客户信息)
* @param orderId
* @return
*/
Order selectOrderWithCustomer(Integer orderId);
}
(2)Mapper映射文件
<select id="selectOrderWithCustomer" resultMap="orderMap">
select o.order_id,o.order_name,o.customer_id,c.customer_name
from t_order o
join t_customer c
on (o.customer_id = c.customer_id)
where order_id = #{orderId}
</select>
<resultMap id="orderMap" type="com.atguigu.entity.Order">
<id column="order_id" property="orderId"></id>
<result column="order_name" property="orderName"></result>
<result column="customer_id" property="customerId"></result>
<!--对一映射使用association -->
<association property="customer" javaType="com.atguigu.entity.Customer">
<id column="customer_id" property="customerId"></id>
<result column="customer_name" property="customerName"></result>
</association>
</resultMap>
如果只使用join而没有使用on指定条件的话,返回的是笛卡尔积,明显是不符合常理的。要避免此类错误。
(3)测试类
@Test
public void testSelectOrderWithCustomer(){
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.selectOrderWithCustomer(1);
System.out.println(order);
System.out.println(order.getCustomer());
}
step5.(多、一)对多
查询某客户的信息,包含可能多个订单信息。
(1)Mapper接口
public interface CustomerMapper {
/**
* 查询指定编号客户(携带订单信息,可能有多个订单)
* @param customerId
* @return
*/
Customer selectCustomerWithOrderList(Integer customerId);
}
(2)Mapper映射文件
<select id="selectCustomerWithOrderList" resultMap="customerMap">
select c.customer_id,c.customer_name,o.order_id,o.order_name
from t_customer c
join t_order o
on c.customer_id = o.customer_id
where c.customer_id=#{customerId}
</select>
<resultMap id="customerMap" type="com.atguigu.entity.Customer">
<id column="customer_id" property="customerId"></id>
<result column="customer_name" property="customerName"></result>
<collection property="orderList" ofType="com.atguigu.entity.Order">
<id column="order_id" property="orderId"></id>
<result column="order_name" property="orderName"></result>
</collection>
</resultMap>
(3)测试类
@Test
public void testSelectCustomerWithOrderList(){
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.selectCustomerWithOrderList(1);
System.out.println(customer);
List<Order> orderList = customer.getOrderList();
orderList.forEach(o-> System.out.println(o));
}
总结
多表查询的优点:
- 一条SQL查询多张表,结果包含多张表的数据
- 速度快
多表查询的缺点:
- 如果只想获取一张表的数据,但是多张表也会查询出来
- 对于一对多来说,这个问题更严重。只要客户名称,不要订单列表,却把订单列表(这是一个集合,多条记录)也查出来。对于多对一,这个问题还不太严重。要查询订单信息,同时把客户信息也查出来(就一个客户端,不是List)
针对这个缺点来说,如果确定要使用多表的数据,那就直接使用多表查询。
4.分步查询
如果使用连接查询(join),只有立即加载,没有延迟加载(懒加载)。数据不管需不需要,都会被查询出来。
如果不确定需不需要多表数据,可以采用更加灵活的延迟加载。要使用延迟加载,必须将连接查询(一条SQL)变成分步查询(多条SQL语句)
sql-1.查询指定编号的客户(不带订单的单表查询)
接口
public interface CustomerMapper {
Customer findById(Integer customerId);
}
映射
<select id="findById" resultType="com.atguigu.entity.Customer">
select * from t_customer where customer_id =#{customerId}
</select>
测试
@Test
public void testFindByCustomerId(){
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.findById(1);
System.out.println(customer);
}
sql2-查询指定编号的客户的订单(只有订单,单表)
接口
public interface OrderMapper {
/**
* 查询指定客户的订单,结果是List
* @param customerId
* @return
*/
List<Order> findByCustomerId(Integer customerId);
}
映射
<select id="findByCustomerId" resultType="com.atguigu.entity.Order">
select * from t_order where customer_id=#{customerId}
</select>
测试
@Test
public void testFindOrderListByCustomerId(){
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> orderList = mapper.findByCustomerId(1);
orderList.forEach(order-> System.out.println(order));
}
sql3-合并
<select id="findById" resultMap="customerMap">
select * from t_customer where customer_id =#{customerId}
</select>
<resultMap id="customerMap" type="com.atguigu.entity.Customer">
<id column="customer_id" property="customerId"></id>
<result column="customer_name" property="customerName"></result>
<collection property="orderList"
select="com.atguigu.mapper.OrderMapper.findByCustomerId"
column="customer_id">
</collection>
</resultMap>
collection 关键属性select指明获取属性方法,column对应传入这个方法的参数。
5.延迟加载
什么是延迟加载
需要其他表信息的时候采取查询订单,不要订单信息的时候,就不查询。
如何实现延迟加载
-
第一步:延迟加载(lazyload 懒加载)全局开关lazyLoadingEnabled
<!--mybatis配置文件--> <settings> <!-- 延迟加载的总开关--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
-
第二步:分开关:fetchType【eager饿汉式,lazy懒汉式】,默认是lazy的。
<!--映射文件--> <resultMap id="customerMap2" type="com.atguigu.entity.Customer"> <id column="customer_id" property="customerId"></id> <result column="customer_name" property="customerName"></result> <collection property="orderList" select="com.atguigu.mapper.OrderMapper.findByCustomerId" column="customer_id" fetchType="eager"> </collection> </resultMap>
如果全局开关和分开关冲突,优先分开关的配置【就近】。
如何确定使用延迟加载还是立即加载
连接(join)查询:一条SQL语句,效率高,不灵活,只有立即加载。
分步查询:多条SQL语句,效率低。灵活,可以设置立即加载还是延迟(通过总开关和分开关)
如果确定多张表的数据都需要,直接使用连接查询。
如果不确定多张表数据是否都需要,可能有些场景需要,有些场景不需要,可以使用分步查询并设置延迟加载(懒加载)。
6.多对多关联和一对一关联
-
数据库中,不管一对一、一对多、多对多都是通过外键来实现的。外键关系其实是一种一对多的关系。
-
多对多的关联实现
-
数据库中使用外键解决:引入一个中间表。将一个多对多的关系,变成两个一对多的关系,中间表是多,之前两个表是一。
-
在MyBatis中如何实现多对多?
-
方案1:可以像数据库表一样,定义三个实体类,在两个原生表和中间表之间建立一对多关系。
-
方案2:还可以在Java类中直接建立多对多关系。以学生-选课表为例。
Course类 增加属性
List<Student> stuList = new ArrayList()
Student类 增加属性
List<Course> courseList = new ArrayList();
映射文件中均使用Collection进行映射。
-
-
一对一关联实现
- 数据库中使用外键解决
- 方案1:外键关联:将外键同时设置为unique,将一对多变成一对一。
- 方案2:主键关联:同时是主键和外键。主键保证了唯一性和非空;外键保证了必须参考另一个类的主键。
- 在MyBatis中如何实现多对多?
- 在java类中声明另一个表的对应类的对象作为属性。在两端的映射文件中都是用association。
- 数据库中使用外键解决
动态SQL语句
有什么优势?
在实际使用过程中,经常会使用到多条件查询,比如购物网站的搜索引擎,但是经常会有部分条件不取值。
在JDBC中,是根据条件是否取值来进行sql语句的拼接,一般是使用StringBuilder类和它的append方法实现的,还需要编码,还是比较繁琐;在MyBatis中,动态SQL是在xml中配置的,便于修改,可以使sql更加灵活。
元素
if语句
<select id="findEmp" resultType="employee">
select * from t_emp where 1=1
<if test="empName!=null and empName!=''">
and emp_name like "%"#{empName}"%"
</if>
<if test="minSalary>0">
and emp_salary >= #{minSalary}
</if>
</select>
技巧:增加一个条件 1=1 ,后面的条件都不是第一个条件,都以and开始
where语句
<select id="findEmp" resultType="employee">
select * from t_emp
<where>
<if test="empName!=null and empName!=''">
and emp_name like "%"#{empName}"%"
</if>
<if test="minSalary>0">
and emp_salary >= #{minSalary}
</if>
</where>
</select>
注意:where会自动的去掉第一个条件的and、or
trim语句
<select id="findEmp" resultType="employee">
select * from t_emp
<trim prefix="where" prefixOverrides="and">
<if test="empName!=null and empName!=''">
and emp_name like "%"#{empName}"%"
</if>
<if test="minSalary>0">
and emp_salary >= #{minSalary}
</if>
</trim>
</select>
prefix属性:指定要动态添加的前缀
suffix属性:指定要动态添加的后缀
prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
choose/when/otherwise标签(switch case)
<select id="findEmp" resultType="employee">
select * from t_emp where
<choose>
<when test="empName!=null and empName!=''">
emp_name like "%"#{empName}"%"
</when>
<when test="minSalary>0">
emp_salary >= #{minSalary}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</select>
不管有多少个条件满足,只执行第一个满足条件的。如果一个条件也没有,就执行otherwise,otherwise不要少。
foreach
public List<Employee> findEmp5(@Param("idArr") int [] idArr);
public List<Employee> findEmp6(@Param("idList")List<Integer> idList);
<!-- Available parameters are [array, arg0]-->
<!-- Available parameters are [idArr, param1]-->
<select id="findEmp5" resultType="employee">
select * from t_emp where emp_id in
<foreach collection="idArr" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- Parameter 'array' not found. Available parameters are [arg0, collection, list] -->
<select id="findEmp6" resultType="employee">
select * from t_emp where emp_id in
<foreach collection="idList" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
@Test
public void testFindEmp2(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//int [] idArr = {1,7,10,20};
//List<Employee> list = mapper.findEmp5(idArr);
List<Integer> idList = new ArrayList<>();
Collections.addAll(idList,1,7,10);
List<Employee> list = mapper.findEmp6(idList);
list.forEach(emp-> System.out.println(emp));
}
注意事项
- 如果参数是数组,没有使用@Param,可以使用array来接收
- 如果参数是List,没有使用@Param,可以使用collection、list来接收
set
针对update操作,会自动的去掉多余的,
public int updateEmp(Employee emp);
<update id="updateEmp">
update t_emp
<set>
<if test="empName!=null and empName!=''">
emp_name = #{empName},
</if>
<if test="empSalary>0">
emp_salary = #{empSalary}
</if>
</set>
where emp_id = #{empId}
</update>
sql
提取SQL语句的公共部分,并使用include标签来引用。便于修改。
<sql id="empColumns">
emp_id,emp_name,emp_salary
</sql>
<select id="findAll" resultType="Employee">
select <include refid="empColumns"></include> from t_emp where 1=1
</select>
缓存
1. 作用
如果缓存中有需要的数据,就直接读取缓存中数据,而不去读取硬盘。减少对硬盘(数据库)的访问次数,提高了效率。
2.MyBatis缓存分类
一级缓存:SqlSession级别 默认开启
二级缓存:SqlSessionFactory级别 默认没有开启
3. MyBatis缓存访问步骤
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- 从数据库查询的数据,会直接放入到一级缓存
- SqlSession关闭之前,一级缓存中的数据会写入二级缓存
4.一级缓存
@Test
public void testCache1(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.findById(1);
System.out.println(emp);
//清空一级缓存
sqlSession.clearCache();
//提交事务,也会清空一级缓存
//sqlSession.commit();
Employee emp2 = mapper.findById(1);
System.out.println(emp2);
Employee emp3 = mapper.findById(2);
System.out.println(emp3);
}
什么时候一级缓存会失效?
- 不是同一个SqlSession
- 同一个SqlSession但是查询条件发生了变化
- 同一个SqlSession两次查询期间手动清空了缓存
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间提交了事务
5.二级缓存
(1)全局开关:默认开启
<settings>
<!-- 二级缓存总开关,默认就是true-->
<setting name="cacheEnabled" value="true"/>
</settings>
(2)局部开关:二级缓存以namespace为单位
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<!--二级缓存的分开关,以namespace为单位 -->
<cache/>
<select id="findById" resultType="employee">
select * from t_emp where emp_id = #{empId}
</select>
</mapper>
二级缓存分开关的设置:
<cache type="" size="" blocking="" eviction="" flushInterval="" readOnly=""/>
- Type:指定具体的二级缓存类型。如果不指定,就使用MyBatis自带的二级缓存。
- Size:代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- flushInterval:刷新(清空缓存)间隔。默认情况是不设置,也就是没有刷新间隔
- readOnly:只读。True:性能高 false,性能低,安全性高
- Eviction:删除。比如二级缓存中最多放100个缓存内容,第101来了,怎么办?
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- Blocking 阻塞:访问某个缓存数据,在访问过程中对key(sql语句)加锁,其他的请求同一个SQL语句,要等待。保证了只有一个请求来访问缓存中的一份数据。
(3)实体类要序列化
什么时候需要序列化:在在内存中存储不需要序列化,如果内存的数据要存入硬盘,或者在网络上传输,就要序列化(其实是变成字节数组)。
一级缓存肯定在内存中,二级缓存可能在内存中,但是二级缓存数据多,也可以存储到外存中,所以存在二级缓存中的数据必须序列化。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable { //t_emp
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary
}
(4)测试
@Test
public void testCache2(){
//由一个工厂创建两个SqlSession
SqlSession sqlSession1 = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
//使用两个SqlSession发送同一个请求
Employee emp1 = mapper1.findById(1);
System.out.println(emp1);
//!!! 关闭SqlSession,将该SqlSession一级缓存中数据存入二级缓存
// sqlSession1.close();
Employee emp2 = mapper2.findById(1);
System.out.println(emp2);
}
整合Ehcache
Ehcache是一种开源的、基于标准的缓存,它可以提高性能、减轻数据库负担并简化可伸缩性。它是使用最广泛的基于Java的缓存,因为它健壮、可靠、功能齐全,并与其他流行的库和框架集成。Ehcache可以从进程内缓存扩展到进程内/进程外混合部署和TB大小的缓存。
1.添加依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
2.配置文件
在resources目录下创建ehcache.xml【必须是这个名字,否则是识别不到的】
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3.加入log4j适配器依赖
存在SLF4J时,是无法直接访问log4j的,需要加入一个适配器类:slf4j-log4j12。
<!-- SLF4j和log4j的适配器类 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
4.指明二级缓存使用Ehcache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
日志框架
日志框架类型
门面
名称 | 说明 |
---|---|
JCL | 陈旧 |
SLFJ | 适合 |
jboss-logging | 特殊专业领域使用 |
实现
名称 | 说明 |
---|---|
log4j | 最初版 |
JUL(java.util.logging) | JDK自带 |
log4j2 | Apache收购log4j后全面重构,内部实现和log4j完全不同 |
logback | 优雅、强大 |
logback使用方式
- 添加依赖
<!-- slf4j日志门面的一个具体实现:logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
-
添加配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="DEBUG"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.atguigu.crowd.mapper" level="INFO"/> </configuration>
缓存源码
Cache相关的API
缓存(一级和二级缓存)的顶级接口:org.apache.ibatis.cache.Cache
缓存的实现类:org.apache.ibatis.cache.impl.PerpetualCache
缓存的(第三方的)实现类:org.mybatis.caches.ehcache.EhcacheCache
缓存的其他实现类:(装饰类)FifoCache,LruCache,SoftCache,WeakCache
装饰模式:
蛋糕接口:(Cache)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(PerpetualCache EhcacheCache)
蛋糕的装饰:卡片、干果、蜡烛(FifoCache、LruCache是添加了装饰的蛋糕)
如果采用继承,就会数不清的实现类。采用装饰模式,可以减少子类的数量,是继承的一种替代方案。现场组装。
蛋糕接口:(InputStream)
蛋糕实现类:奶油蛋糕 冰激凌蛋糕(FileInputStream ByteArrayInputStream)
蛋糕的装饰:卡片、干果、蜡烛(BufferedInputStream DataInputStream)
InputStream is = new FileInputStream(“readme.txt”);
BufferedInputStream bis = new BufferedInputStream(is );
DataInputStream dis = new DataInputStream(bis);
DataInputStream dis =
new DataInputStream(new BufferedInputStream( new FileInputStream(“readme.txt”)));
Cache内部有一个Map,存储缓存的信息
所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。
org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过PerpetualCache来操作缓存数据的。但是这就奇怪了,同样是PerpetualCache这个类,怎么能区分出来两种不同级别的缓存呢?
其实很简单,调用者不同。
· 一级缓存:由BaseExecutor调用PerpetualCache
· 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰
2.一级Cache的源码
3. 二级Cache的源码
使用默认的二级Cache:PerpetualCache
使用第三方的EhCacheCache
源码:
MyBatis的缓存查询过程:
先查询二级缓存,
二级缓存中没有,再查询一级缓存。
一级缓存中没有,就查询数据库
从数据库中查询到数据,放入到一级缓存。
逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:实体类、Mapper接口、映射文件
使用
1.添加插件
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
2.添加配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis-example"
userId="root"
password="root">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.entity" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="mappers" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Employee"/>
<table tableName="t_customer" domainObjectName="Customer"/>
<table tableName="t_order" domainObjectName="Order"/>
</context>
</generatorConfiguration>
3. 开始逆向
QBC(了解)
QBC:Query By Criteria
QBC查询最大的特点就是将SQL语句中的WHERE子句进行了组件化的封装,让我们可以通过调用Criteria对象的方法自由的拼装查询条件。
Mapper映射
package向mybatis-config.xml引入mapper映射文件
<mappers>
<!-- 指定映射文件的位置:映射文件中进行ORM映射-->
<package name="com.atguigu.mapper"/>
</mappers>
前提
- Mapper接口和Mapper配置文件名称一致
- Mapper配置文件放在Mapper接口所在的包内
插件机制(了解)
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。著名的Mybatis插件包括 PageHelper(分页插件)、通用 Mapper(SQL生成插件)等。
如果想编写自己的Mybatis插件可以通过实现org.apache.ibatis.plugin.Interceptor接口来完成,表示对Mybatis常规操作进行拦截,加入自定义逻辑
但是由于插件涉及到Mybatis底层工作机制,在没有足够把握时不要轻易尝试。
类型处理器typeHandler(了解)
1、Mybatis内置类型处理器
2、Mybatis自定义类型处理器步骤
1.自定义类型转换器
public class AddressTypeHandler extends BaseTypeHandler<Address> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) throws SQLException {
}
@Override
public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
//获取当前列的数据
String str = rs.getString(columnName);//"河北省,张家口市,崇礼县"
//处理该列的数据转换为Address
String[] arr = str.split(",");
Address address = new Address(arr[0],arr[1],arr[2]);
//返回Address
return address;
}
@Override
public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
@Override
public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
2.在配置文件中进行注册
<typeHandlers>
<typeHandler handler="com.atguigu.typehandler.AddressTypeHandler"
javaType="com.atguigu.entity.Address"
jdbcType="VARCHAR"></typeHandler>
</typeHandlers>
3.修改实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String province;
private String city;
private String county;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable { //t_emp
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary
//private String empAddress;
private Address empAddress;
}
MyBatis底层对JDBC的封装(重要)
目前为止,还没有看见过Connection、Statement、ResultSet。
问题1:SqlSessionFactory就相当于DriverMangaer,SqlSession就相当于Connection,Mapper就相当于Statement??
这个说法完全错误。
SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor(BaseExecutor和CacheingExecutor)。
1. Mybatis底层四大对象:
四大对象:SqlSessionFactory SqlSession Mapper ?? 错了,都错了
四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler
①Executor
执行器,由它来调度StatementHandler、并由StatementHandler来调度ParameterHandler、ResultSetHandler等来执行对应的SQL。
②StatementHandler
使用数据库的Statemet(PreparedStatement)执行操作
③ParameterHandler
处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。
④ResultSetHandler
处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。
2. Mybatis底层四大步骤:
SimpleExecutor中
doQuery() select ------------>JDBC executeQuery()
doUpdate() insert update delete--------->jDBC executeUpdate()
核心步骤就是四步:
第一步:获取数据库连接。
第二步:调用StatementHandler的prepare()方法,创建Statement对象并设置其超时、获取的最大行数等。
第三步:调用StatementHandler的parameterize()方法。负责将具体的参数传递给SQL语句。
第四步:调用StatementHandler的query()/update方法。完成查询/DML操作,并对结果集进行封装处理,然后返回最终结果。
第一步:获取数据库连接 Connection connection = getConnection(statementLog);
第二步:创建Statement(PreparedStatement) Statement stmt = handler.prepare(connection, transaction.getTimeout());
第三步:处理参数 handler.parameterize(stmt);
第四步:执行CRUD操作 return handler.update(stmt);// insert update delete 返回 int return handler.query(stmt, resultHandler) ;s //select 返回List 底层会用ResultSetHandler