MyBatis授课笔记
1 MyBatis概述
2 总体技术体系
①单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。
②分布式架构
一个项目,拆分成很多个模块,每个模块是一个工程。每一个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。
强调:SpringCloud离不开SpringBoot。每个微服务就是使用SpringBoot开发的,而各个微服务整体管理交给SpringCloud。
3 认识框架 framework
生活案例
框架就是毛坯房,半成品。
SSM就是免费的毛坯房,大浪淘沙,Java领域中最流行的框架。
框架=框(限制 统一了风格)+架(支撑 保证质量、提高开发速度)
技术角度:
框架=jar包(特定问题的固定的解决方案)+配置文件(个性化,可变,比如数据库的四个连接参数、SQL语句、访问路径)
常用的基于JavaEE的三大开源框架,已经从SSH、SSH2过渡到了SSM:SpringMVC、Spring、MyBatis。
总之,框架是一个半成品,已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。
4 认识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(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录
Mybatis是一个半自动的ORM(Object Relation Mapping)框架
5 认识ORM
O:Object 对象
R:relational 关系
M:Mapping 映射
Java类——数据库表
Java类的成员变量—-数据库表的字段
Java类的对象——数据库表的记录
6 MyBatis和Hibernate的对比
而MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。完成同样功能的两条SQL语句的性能可能相差十几倍到几十倍,在高并发、快响应要求下的互联网系统中,对性能的影响更明显。
总之,因为MyBatis具有封装少、映射多样化、支持存储过程、可以进行SQL语句优化等特点,符合互联网高并发、大数据、高性能、高响应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架。而对于对性能要求不高的比如内部管理系统、ERP等可以使用Hibernate。
7 MyBatis入门(Hello World)
8 创建数据库表(物理建模)
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 |
---|
9 创建maven项目和实体类(逻辑建模)
使用MyBatis是否必须是Web项目?不是的。只要是可以使用JDBC的地方都可以使用MyBatis
使用Spring也不要求必须是Web项目
但是使用SpringMVC必须是Web项目,它对Servlet进行了封装。
@Data @AllArgsConstructor @NoArgsConstructorpublic class Employee { //t_emp private Integer empId;//emp_id private String empName;//emp_name private Double empSalary;//emp_salary} |
---|
10 添加依赖(pom.xml)
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.3</version> </dependency> </dependencies> |
---|
11 创建配置文件(唯一的全局配置文件)
习惯上命名为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”> <transactionManager type=”JDBC”/> <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> </mappers> </configuration> |
---|
12 创建映射文件(会有多个,主要内容就是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 namespace=”a.b.c”> <select id=”findAll” resultType=”com.atguigu.entity.Employee”> select _ _from t_emp </select> </*mapper> |
---|
13 代码开发
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 //4.处理结果 list.forEach((emp)-> System.out.println(emp)); //关闭资源 sqlSession.close(); //factory.close(); } } |
---|
14 运行测试
问题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> <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的多个配置
解决2:给Maven指定全局配置或者项目的局部配置
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> |
---|
问题4:我感觉MyBatis也不简单
这是在搭建项目框架,很多操作只需要在此处做一次。搭建好框架后,其实的操作就是:
在映射文件中写sql语句和在测试类调用sql语句。
之前在JDBC中SQL语句写在代码中,现在SQL语句写在映射文件中,便于修改。
//仔细的体会一下:忽略了结果集处理的细节,直接返回List List list = sqlSession.selectList(“a.b.c.findAll”);
15 总结和底层细节
误区:SQL文件在xml文件,如果每次要执行该SQL语句时都去读取xml文件,效率太低。
解释:创建SqlSessionFactory时,会将配置文件和映射文件的信息都读到内存中,按照特定的结构来存在,不会每次都读硬盘。
16 MyBatis入门完善(Hello World)
17 添加日志log4j
- 添加依赖
| <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency> | | —- |
- 准备配置文件,比如在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> | | —- |
- 观察效果(出现了SQL语句的DEBUG日志)
- 理解配置文件
- Appender:目的地 日志写到哪里 ConsoleAppender 控制台 org.apache.log4j.FileAppender 写到文件
- Layout:格式 日志写出什么样子 PatternLayout 指定格式 SimpleLayout HTMLLayout
- Level:日志级别 FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
log4j曾经是最流行的日志框架,但是已经不再维护;现在建议使用log4j2和logback。
18 提取连接参数属性文件
- 提取属性文件。要写在resources目录下,名称任意 | wechat.dev.driver=com.mysql.jdbc.Driverwechat.dev.url=jdbc:mysql://localhost:3306/mybatis-examplewechat.dev.username=rootwechat.dev.password=root | | —- |
- 在配置文件中引入并使用属性文件
<properties resource=”jdbc.properties”></properties><environments default=”development”> <environment id=”development”> <transactionManager type=”JDBC”/> <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> |
---|
19 MyBatis单表开发1-无接口方式
20 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(); } |
注意事项: 1. 映射文件使用#{}来接收参数,参数名任意,但是建议见名知义 1. 测试类中调用selectOne(),需要指定具体的参数 |
21 save
<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方法,就会使用使用反射直接操作同名的成员变量 |
22 update
<update id=”updateEmp”> update t_emp set emp_name=#{empName},emp_salary=#{empSalary} where emp_id =#{empId} </update> |
---|
@Testpublic 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(); } |
23 delete
<insert id=”deleteEmp”> delete from t_emp where emp_id = #{abc} </insert> |
---|
@Testpublic 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(); } |
24 更多细节
- 映射文件中使用delete、update、insert标签没有区别,代码中调用SqlSession的insert、update、delete方法没有区别,关键是SQL语句
- 因为不管调用insert、update、delete哪个方法,底层调用的都是update(),更底层都是调用JDBC的executeUpdate()
- 不管查询是调用的是selectList(),还是selectOne(),底层调用的都是selectList.
- 目前已经学习了SqlSession的哪些方法
- selectList
- selectOne
- insert
- update
- delete
- Commit
- MyBatis的常用API
- SqlSessionFactory
- SqlSession: 它是Connection,还是Statement呢?都不是!!!!
25 MyBatis单表开发2-Mapper代理接口方式
前面已经使用MyBatis完成了对Emp表的CRUD操作,都是由SqlSession调用自身方法发送SQL命令并得到结果的,实现了MyBatis的入门。 但是却存在如下缺点: 1).不管是selectList()、selectOne()、selectMap(),都只能提供一个查询参数。如果要多个参数,需要封装到JavaBean中,并不一定永远是一个好办法。 2).返回值类型较为固定 3).只提供映射文件,没有提供数据库操作的接口,不利于后期的维护扩展,也和之前开发习惯不符
在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式。在实际开发中也使用该方式。
Mybatis中的Mapper接口相当于以前的Dao。但是区别在于,Mapper仅仅是接口,我们不需要提供实现类
26 定义接口 相当于使用JDBC时的DAO接口,但是不需要写实现类
| public interface EmployeeMapper {
public List
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);<br />} |
| —- |
27 映射文件
<mapper namespace=”com.atguigu.mapper.EmployeeMapper”> <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
28 测试
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 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代理(接口绑定)
29 给SQL语句传参的两种方式
30 使用#{} 底层使用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> |
---|
31 使用${} 底层使用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> |
---|
32 使用${}和#{}实现模糊查询
<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> |
---|
33 数据输入
讲解的是方法的参数可以是一个还是多个,是基本类型还是复杂类型,在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> |
---|
34 单个简单参数(Integer、Double、String)
public Employee findById(Integer empId);public List |
---|
<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}”%”,要求是双引号。 |
35 单个引用参数(Employee)
public int saveEmp(Employee emp); |
---|
<insert id=”saveEmp”> insert into t_emp values(default,#{empName},#{empSalary}) </insert> |
底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术 如果没有getter方法,就会使用使用反射直接操作同名的成员变量 |
36 单个引用参数(Map)
public List |
---|
<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> |
@Testpublic void testFindEmp2(){ EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Map map = new HashMap<>(); map.put(“ename11”,“oh”); map.put(“minSalary”,400); List list.forEach((emp)-> System.out.println(emp)); } |
#{key}使用Map的key来接收该key对应的value |
37 多个简单参数(Integer、Double、String)
public List |
---|
<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> |
| Parameter ‘ename’ not found. Available parameters are [arg1, arg0, param1, param2] |
38 多个引用参数(Employee,Dept)
public int saveEmp2(Employee emp, Dept dept); public int saveEmp3(@Param(“emp”)Employee emp, @Param(“dept”) Dept dept); |
---|
<insert id=”saveEmp2”> insert into t_emp values(default,#{param1.empName},#{param1.empSalary},#{param2.deptno}) </insert> |
<insert id=”saveEmp3”>
insert into t_emp values(default,#{emp.empName},#{emp.empSalary},#{dept.deptno})
</insert> |
39 到底该使用哪个
如果有多个参数
参数数量不多(3,4个) 建议使用
public List
参数数量多(大于4个),将多个参数封装到一个实体类中
public int saveEmp(Employee emp);