一 小黑子对mybatis进行概述
-
在文献中看到的framework被翻译为框架
-
Java常用框架:
- SSM三大框架: Spring + SpringMVC + MyBatis
- SpringBoot
- SpringCloud等。。。
-
框架其实就是对通用代码的苗装,提前写好了一堆接口和类,我们可以在做项目的时候直接引入这些接口和类(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率.
-
框架一般都以jar包的形式存在。(jar包中有class文件以及各种配置文件等。)
-
SSM三大框架的学习顺序:MyBatis、Spring、SpringMVC
MyBatis是一个持久层框架,负责项目开发中的数据访问层(DAO层)
具体来说,MyBatis提供以下功能和特点,用于实现数据访问层的开发:
SQL映射:MyBatis通过XML配置文件或注解的方式,将SQL语句与Java方法进行映射,实现了对象关系映射(ORM),将数据库操作封装成Java方法调用。
参数绑定:MyBatis支持将Java对象、基本类型和Map等作为参数传递给SQL语句,实现了方便灵活的参数绑定。
结果映射:MyBatis可以将查询结果映射为Java对象或基本类型,并支持关联查询和复杂对象图的映射。
缓存机制:MyBatis提供了一级缓存和二级缓存的支持,能够减少对数据库的频繁访问,提高系统性能。
插件扩展:MyBatis提供了插件机制,允许开发者编写自定义插件,拦截并修改MyBatis的核心行为,实现功能的扩展和定制。
总的来说,MyBatis作为一个优秀的持久层框架,主要负责数据访问层的开发,通过SQL映射、参数绑定和结果映射等功能,简化了与数据库的交互,提高了开发效率和代码的可维护性。
1.1 mybatis三层架构
- 表现层(UI)︰直接跟前端打交互(一是接收前端ajax请求,二是返回json数据给前端)
- 业务逻辑层(BLL)︰一是处理表现层转发过来的前端请求(也就是具体业务),二是将从持久层获取的数据返回到表现层。
- 数据访问层(DAL)︰直接操作数据库完成CRUD,并将获得的数据返回到上一层(也就是业务逻辑层)。
- Java持久层框架
- Mybatis
- Hibernate(实现了JPA规范)
- jOOQ
- Guzz
- Spring Data(实现了JPA规范)
- ActiveJDBC…
1.1 了解MyBatis
- MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。
- MyBatis在三层架构中负责持久层的,属于持久层框架。
- MyBatis的发展历程:
- MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。.2013年11月迁移到Gitub,
- iBATIS一词来源于“internet”和"abatis”的组合,是一个基于Java的持久层框架。iBATS提供的持久层框架包括SQL Maps和Data Access Obijects(DAOs),
- 打开mybatis代码可以看到它的包结构中包含: ibatis
MyBatis框架、Spring框架和Spring MVC框架在项目开发中有一些区别,下面是它们的主要区别:
- 定位和职责:
- MyBatis框架:主要用于数据访问层的开发,提供了强大的SQL映射功能,可以将数据库操作封装成Java方法调用。
- Spring框架:是一个综合性的企业级开发框架,提供了多种功能和特性,包括IoC容器、AOP、事务管理、Web开发等。
- Spring MVC框架:是Spring框架的一部分,用于开发Web应用程序的MVC模式,负责接收用户请求、处理逻辑和渲染视图。
- 使用场景:
- MyBatis框架:适用于需要编写复杂SQL语句或对数据库操作有较高灵活性要求的项目,如需要进行复杂数据查询、批量操作等。
- Spring框架:适用于各种企业级应用项目,提供了综合的解决方案,包括依赖注入、面向切面编程、事务管理等。
- Spring MVC框架:适用于开发基于Web的应用程序,将请求分发给不同的控制器处理,并将处理结果渲染为视图返回给用户。
- 功能和特性:
- MyBatis框架:提供了强大的SQL映射、参数绑定和结果映射功能,可以直接编写和调优SQL语句,对数据库操作的细粒度控制较高。
- Spring框架:提供了IoC容器、AOP、事务管理、面向切面编程等功能,支持各种应用层面的开发需求,并简化了开发流程。
- Spring MVC框架:提供了基于MVC的Web开发模式,包括请求处理、URL映射、视图渲染等功能,支持Restful风格的开发。
- 整合方式:
- MyBatis与Spring的整合:通过Spring提供的MyBatis-Spring模块将二者整合在一起,实现配置的统一和依赖注入的管理。
- Spring与Spring MVC的整合:Spring MVC是Spring框架的一部分,可以直接在Spring框架中使用Spring MVC的功能,无需额外的整合。
- MyBatis与Spring MVC的整合:可以将MyBatis作为Spring MVC的持久化框架,结合@RequestMapping注解进行数据库操作。
总的来说,MyBatis框架主要关注数据访问层开发,Spring框架提供了更多的企业级特性和全面的解决方案,而Spring
MVC框架则专注于Web应用程序的开发。在实际项目中,可以根据需求选择合适的框架及其整合方式,以实现高效、灵活和可维护的应用程序开发
1.3 ORM思想
- ORM:对象关系映射
- O(Object):Java虚拟机中的Java对象。
- R(Relational):关系型数据库
- M(Mapping):将Java虚拟机中的Java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象。
ORM图示:
MyBatis框架特点:
- 支持定制化 SQL、存储过程、基本映射以及高级映射
- 避免了几乎所有的 JDBC 代码中手动设置参数以及获取结果集
- 支持XML开发,也支持注解式开发。【为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。】
- 将接口和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录
- 体积小好学:两个jar包,两个XML配置文件。
- 完全做到sql解耦合。
- 提供了基本映射标签。
- 提供了高级映射标签。
- 提供了XML标签,支持动态SQL的编写。
…
二 小黑子的MyBatis入门程序
会JDBC,MyBatis就可以学
2.1 Mybatis程序的编写
resources目录:
放在这个目录当中的,一般都是资源文件,配置文件。直接放到resources目录下的资源,等同于放到了类的根路径下。
- 步骤1:打包方式:jar(不需要war,因为mybatis封装的是jdbc。)
<groupId>com.powernode</groupId>
<artifactId>mybatis-001-introduction</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
- 步骤2:引入依赖(mybatis依赖 + mysql驱动依赖)
<!--mybatis核心依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
-
步骤3:编写mybatis核心配置文件:mybatis-config.xml,在resources根目录下新建mybatis-config.xml配置文件
- 从XNL中构建SqlSessionFactory
通过官方的这句话,能想到什么呢?- 第一:在MyBatis中一定是有一个很重要的对象,这个对象是:SqlsessionFactory对象。
- 第二:SqlSessionFactory对象的创建需要XML。
- 从XNL中构建SqlSessionFactory
XNL是什么?
它一定是一个配置文件。
<?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.cj.jdbc.Driver"/><!--修改的地方-->
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/> <!--自己的路径-->
<property name="username" value="root"/><!--自己的用户名-->
<property name="password" value="root"/><!--自己设置的密码-->
</dataSource>
</environment>
</environments>
<mappers>
<!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
<mapper resource=""/>
</mappers>
</configuration>
注意:
- 第一:这个文件名不是必须叫做mybatis-config.xml,可以用其他的名字。只是大家都采用这个名字。
- 第二,这个文件存放的位置也不是固定的,可以随意,但一般情况下,会放到类的根路径下。
mybatis-config.xml文件中的配置信息不理解没关系,先把连接数据库的信息修改以下即可。其他的别动。
- 步骤4:编写XxxxMapper . xml文件在这个配置文件当中编写SQL语句
在resources根目录下新建CarMapper.xml配置文件- 这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper .xml把它暂时放到类的根路径下
<?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">
<!--namespace先随意写一个-->
<mapper namespace="car">
<!-- insert语句:id是这条SQL语句的唯一标识。这个id就代表了这条SQL语句 insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,'102','丰田mirai',40.30,'2014-10-05','氢能源')
</insert>
</mapper>
- 步骤5:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:
<mapper resource="CarMapper.xml"/>
注意:resource属性会自动从类的根路径下开始查找资源。
- 注意1:sql语句最后结尾可以不写“;”
- 注意2:CarMapper.xml文件的名字不是固定的。可以使用其它名字。
- 注意3:CarMapper.xml文件的位置也是随意的。这里选择放在resources根下,相当于放到了类的根路径下。
- 注意4:将CarMapper.xml文件路径配置到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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
- 步骤6:编写MyBatisIntroductionTest代码(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
在MyBatis当中,负责执行SQL语句的那个对象叫什么?- SqlSession
- SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话
- 要想获取SqlSession对象,需要先获取SqlSeesionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象
- 怎么获取SqlSessionFactory对象呢?
- 需要首先获取SqlSessionFactoryBuilder对象
- 通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象
mybatis的核心对象包括:
sqlSessionFactoryBuilder
sqlSessionFactory
sqlsession
sqlSessionFactoryBuilder --> sqlsessionFactory --> sqlSession
package com.powernode.mybatis.test;
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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisIntroductionTest {
public static void main(String[] args) throws IOException {
//1.获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.获取SqlSessionFactory对象,通过传入输入流的文件路径在工厂builder.build方法来进行创建
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");//填写mybatis-config.xml文件的路径
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//3.获取SqlSession对象,通过工厂的openSession方法进行获取
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.执行SQL语句
int count = sqlSession.insert("car");//放入设置的mapper文件里sql语句的id
//返回值是影响数控表当中的记录条数
System.out.println("插入了几条记录:" + count);
//5.然后手动提交,因为SqlSession不支持自动提交
sqlSession.commit();
//6.关闭资源,(只关闭是不会提交的)
sqlSession.close();
}
}
注意1:默认采用的事务管理器是:JDBC。JDBC事务默认是不提交的,需要手动提交
2.2 关于程序的小细节
mybatis中sql语句的结尾;
可以省略
-
Resources.getResourceAsStream小技巧:
以后繁琐遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载的。- 优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windowst移植到Linux,代码不需要修改,因为这个资源文件一直都在类路径当中
-
InputStream is = new FileInputStream("d ::\\mybatis-config.xml");
采用这种方式获取也可以- 缺点:可移植性太差了,路径定死了,可能会移植到其他的操作系统当中。,导致以上路径无效,还需要修改java代码中的路径,违背了OCP原则
所以:
- mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其他的名字
- mybatis核心配置文件存放的路径,也不一定是再类的根路径下。可以放到其他位置。但是为了项目的可移植性,最好将这个配置文件放到类路径下
系统类加载器有一个方法叫做:getResourceAsStream
它就是从类路径当中加载资源的。
通过源代码分析发现:
InputStream is = Resources.getResourceAsStream( "mybatis-config.xml");
底层的源代码其实就是:
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
CarMapper.xml文件的名字是固定的吗?
都不是固定的。
<mapper resource="CarHapper .xml"/>
resource属性:这种方式是从类路径当中加载资源。
<mapper url="file:///d:/ CarMapper.xml"/>
url属性:这种方式是从绝对路径当中加载资源。
2.3 mybatis事务管理机制解析
在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理<transactionManager type="JDBC"/>
- type属性的值包括两个:
- JDBC(jdbc)
- MANAGED(managed)
- type后面的值,只有以上两个值可选,不区分大小写
在mybatis中提供了两种事务管理:
- 第一种:JDBC事务管理
- 第二种:MANAGED事务管理
JDBC事务管理器:
- mybatis框架自己管理事务,自己采用元素的JDBC代码去管理事务:
- conn.setAutoCommit(false);开启事务
- …业务处理…
- conn.commit();手动提交事务
- 使用JDBC事务管理器的话,底层创建的事务管理器对象:jdbctransaction对象
- 如果编写的代码是下面的代码:
SqlSession sqlSession = sqlSessionFactory.openSession(true);
- 表示没有开启事务。因为这种方式压根不会执行:
conn.setAutoCommit(false);
- 在JDBC事务种,没有执行
conn.setAutoCommit(false);
那么autoCommit就是true - 如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次
MANAGED事务管理器:
-
mybatis不再负责事务的管理了。事务管理交给其他容器来负责。例如:spring—— 我不管事务了,你来负责
-
对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED
那么事务这块是没人管的。没有人管理事务表示事务压根没有开启
public class MyBatisIntroductionTest {
public static void main(String[] args) throws IOException {
//1.获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.获取SqlSessionFactory对象,通过传入输入流的文件路径在工厂builder.build方法来进行创建
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");//填写mybatis-config.xml文件的路径
//Resources.getResourceAsStream默认就是从类的根路径下开始查找资源
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);//一般情况下都是一个数据库对应一个SqlsessionFactory对象
//3.获取SqlSession对象,通过工厂的openSession方法进行获取
// SqlSession sqlSession = sqlSessionFactory.openSession(); //如果使用的事务管理器是JDBC的话,底层实际上会执行: conn.setAutoCommit(false);
//这种方式实际上是不建议的,因为么一开启事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.执行SQL语句
int count = sqlSession.insert("car");//放入设置的mapper文件里sql语句的id
//返回值是影响数控表当中的记录条数
System.out.println("插入了几条记录:" + count);
//manager时
//5.然后手动提交,因为SqlSession不支持自动提交
// sqlSession.commit(); 如果使用的事务管理器是JDBC的话,底层实际上还是会执行conn.commit();
//6.关闭资源,(只关闭是不会提交的)
sqlSession.close();
}
}
JDBC的事务:
- 如果没有在JDBC代码种执行:
conn.setAutoCommit(false);
的话,默认的autoCommimt是true
完整的mybatis程序展示:
package com.powernode.mybatis.test;
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;
public class MyBatisCompleteTestjava {
//完整的写法
public static void main(String[] args) {
SqlSession sqlSession =null;
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
//开启会(底层会开启事务)
sqlSession = sqlSessionFactory.openSession();
//执行SQL语句,处理相关业务
int count = sqlSession.insert("car");
//执行到这里,没有发生任何异常,提交事务,终止事务
sqlSession.commit();
} catch (IOException e) {
//最好回滚事务
if(sqlSession != null){
sqlSession.rollback();
}
e.printStackTrace();
} finally {
//关闭会话(释放资源)
if(sqlSession != null){
sqlSession.close();
}
}
}
}
2.4 开发中的junit
- JUnit是专门做单元测试的组件。
- 在实际开发中,单元测试一般是由我们Java程序员来完成的。
- 我们要对我们自己写的每一个业务方法负责任,要保证每个业务方法在进行测试的时候都能通过。
- 测试的过程中涉及到两个概念:
- 期望值
- 实际值
- 期望值和实际值相同表示测试通过,期望值和实际值不同则单元测试执行时会报错。
- 这里引入JUnit是为了代替main方法。
使用JUnit步骤:
- 第一步:引入依赖
<!-- junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
- 第二步:编写单元测试类【测试用例】,测试用例中每一个测试方法上使用
你要测试的类名+Test
注解进行标注。- 测试用例的名字以及每个测试方法的定义都是有规范的:
- 测试用例的名字:XxxTest
- 测试方法声明格式:public void test业务方法名(){}
- @Test注解非常重要,被这个注解标注的方法就是一个单元测试方法
// 测试用例
public class CarMapperTest{
// 测试方法
@Test
public void testInsert(){
//单元测试中有两个重要的概念:
//一个是:实际值(被测试的业务方法的真正执行结果)
//一个是:期望值(执行了这个业务方法之后,你期望的执行结果是多少)
}
@Test
public void testUpdate(){}
}
- 第三步:可以在类上执行,也可以在方法上执行
- 在类上执行时,该类中所有的测试方法都会执行。
- 在方法上执行时,只执行当前的测试方法。
案例:
import com.powernode.junit.service.MathService;
import org.junit.Assert;
import org.junit.Test;
public class MathServiceTest {
@Test
public void testSum(){
//单元测试中有两个重要的概念:
//一个是:实际值(被测试的业务方法的真正执行结果)
//一个是:期望值(执行了这个业务方法之后,你期望的执行结果是多少)
MathService mathService = new MathService();
//获取实际值
int actual = mathService.sum(1, 2);
//期望值
int expected = 3;
//加个断言进行测试
Assert.assertEquals(expected,actual);
}
@Test
public void testSub(){
MathService mathService = new MathService();
int actual = mathService.sub(10, 5);
int expected = 5;
Assert.assertEquals(expected,actual);
}
}
2.5 Mybatis 集成日志框架logback
关于mybatis集成日志组件,让我们调试起来更加方便
-
mybatis常见的集成的日志组件有哪些呢
- SLF4J(沙拉风):沙拉风是一个日志标准,其中有一个框架叫做logback,它实现了沙拉风规范
- LOG4J
- STDOUT_LOGGING…
-
其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准只要开启即可。
怎么开启呢?
在
mybatis-config.xml
文件中使用settings标签
进行配置开启。
例如:<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
- 这个标签在编写的时候需要注意,它应该出现在environments标签之前。注意顺序。当然,不需要记忆这个顺序。因为又dtd文件进行约束,只要参考dta约束即可
- 这种实现也是可以的,可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的
但是没有详细的日期,线程名字等。如果你想使用更加丰富的配置,可以集成第三方的log组件
标准日志也可以用,但是配置不够灵活,可以集成其他的日志组件,例如:log4j,logback等。
logback是目前日志框架中性能较好的,较流行的,所以我们选它。logback日志框架实现了slf4j标准
引入logback的步骤:
- 第一步:映入logback的依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
- 第二步:引入logback所必须的xml配置文件(文件名叫做logback.xml或logback-test.xml,放到类路径当中)
- 这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其他的名字
- 这个配置文件必须放到类的根路径下。不能是其他位置
- 主要配置日志输出相关的级别以及日志具体的格式
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--mybatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
开启的日志:
2.6 Mybatis工具类的编写
创建utils包:
package com.powernode.mybatis.utils;
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;
//mybatis工具类
public class SqlSessionUtil {
//工具类的构造方法一般都是私有化的
//工具类中的所有方法都是静态的,直接采用类名即可调用。不需要new对象
// 为了防止new对象,构造方法私有化
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
//类加载时执行
//SqlSessionUtil工具类在进行第一次加载的时候,
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
// public static SqlSession openSession() throws IOException {
// SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// //SqlSessionFactory对象:一个SqlSessionFactory对于一个environment,一个environment通常是一个数据库环境
// SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// SqlSession sqlSession = sqlSessionFactory.openSession();
// return sqlSession;
// }
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}
测试:
@Test
public void testInsertCarByUtil(){
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.insert("car");
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
三 小黑子教你MyBatis如何完成CRUD(增删改查)
什么是CRUD?
- C:create 增
- R:retrieve 查(检索)
- U:update 改
- D:delete 删
准备工作
- 创建module(Maven的普通Java模块):
mybatis-002-crud
- pom.xml
- 打包方式jar
- 依赖:
- mybatis依赖
- mysql驱动依赖
- junit依赖
- logback依赖
mybatis-config.xml
放在类的根路径下CarMapper.xml
放在类的根路径下logback.xml
放在类的根路径下- 提供
com.powernode.mybatis.utils.SqlSessionUtil
工具类 - 创建测试用例:
com.powernode.mybatis.CarMapperTest
3.1 mybatis完成insert使用Map集合传参
使用MAP集合传参
分析以下SQL映射文件中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">
<!--namespace先随便写-->
<mapper namespace="car">
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
</insert>
</mapper>
这样写存在的问题是:
- SQL语句中的值不应该是写死到配置文件里,值应该是用户提供的,这样子写在实际开发中不存在。
- 一定是要前端的form表单提交过来数据,然后将值传递给sql语句
之前的JDBC代码是这样写的:
// JDBC中采用 ? 作为占位符。那么MyBatis中会使用什么作为占位符呢?
String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(?,?,?,?,?)";
// ......
// 给 ? 传值。那么MyBatis中应该怎么传值呢?
//例如:
ps.setString(1,"103");
ps.setString(2,"奔驰E300L");
ps.setDouble(3,50.3);
ps.setString(4,"2022-01-01");
ps.setString(5,"燃油车");
在MyBatis中可以这样做:
- 在Java程序中,将数据放到Map集合中
- 在sql语句中使用
#{map集合的key}
来完成传值,#{ }
等同于JDBC中的?
,#{}
就是占位符
范例:
CarMapper.xml:
<?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">
<!--namespace先随意写一个-->
<mapper namespace="car">
<!-- insert语句:id是这条SQL语句的唯一标识。这个id就代表了这条SQL语句 insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,#{k1},#{k2},#{k3},#{k4},#{k5})#放入map集合的key
</insert>
</mapper>
测试代码:
package com.powernode.mybatis.test;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
public class CarMapperTest {
@Test
public void testInsertCar(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//假装前端传过来数据了
//这个对象我们先使用Map集合进行数据的封装
HashMap<String, Object> map = new HashMap<>();
map.put("k1","1111");
map.put("k2","byd");
map.put("k3",10.0);
map.put("k4","2020-11-11");
map.put("k5","电车");
//执行sql语句
//insert方法的参数
//第一个参数:sqlid,从CarMapper.xml文件中复制
//第二个参数:封装数据对象
int count = sqlSession.insert("insertCar", map);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
}
所以java程序中使用Map可以给sql语句的占位符传值:
HashMap<String, Object> map = new HashMap<>();
map.put("k1","1111");
map.put("k2","byd");
map.put("k3",10.0);
map.put("k4","2020-11-11");
map.put("k5","电车");
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{k1},#{k2},#{k3},#{k4},#{k5})
注意:#{这里写什么?写map集合的key,如果key不存在,获取的是null}
因此,一般map集合的key起名的时候要见名知意
如下:
<?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">
<!--namespace先随意写一个-->
<mapper namespace="car">
<!-- insert语句:id是这条SQL语句的唯一标识。这个id就代表了这条SQL语句 insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})#放入map集合的key
</insert>
</mapper>
package com.powernode.mybatis.test;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
public class CarMapperTest {
@Test
public void testInsertCar(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//假装前端传过来数据了
//这个对象我们先使用Map集合进行数据的封装
HashMap<String, Object> map = new HashMap<>();
map.put("carNum","1111");
map.put("brand","byd");
map.put("guidePrice",10.0);
map.put("produceTime","2020-11-11");
map.put("carType","电车");
//执行sql语句
//insert方法的参数
//第一个参数:sqlid,从CarMapper.xml文件中复制
//第二个参数:封装数据对象
int count = sqlSession.insert("insertCar", map);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
}
3.1.1 使用POJO集合传参
java程序中使用POJO类给SQL语句的占位符传值
- 第一步:定义一个pojo类Car,提供相关属性。
package com.powernode.mybatis.pojo;
//封装汽车相关信息的pojo类,普遍的java类
public class Car {
// 数据库表当中的字段应该和pojo类的属性一一对应
//建议使用包装类Long,这样可以防止null的问题
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
@Override
public String toString() {
return "Car{" +
"id=" + id +
", carNum='" + carNum + '\'' +
", brand='" + brand + '\'' +
", guidePrice=" + guidePrice +
", produceTime='" + produceTime + '\'' +
", carType='" + carType + '\'' +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCarNum() {
return carNum;
}
public void setCarNum(String carNum) {
this.carNum = carNum;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getGuidePrice() {
return guidePrice;
}
public void setGuidePrice(Double guidePrice) {
this.guidePrice = guidePrice;
}
public String getProduceTime() {
return produceTime;
}
public void setProduceTime(String produceTime) {
this.produceTime = produceTime;
}
public String getCarType() {
return carType;
}
public void setCarType(String carType) {
this.carType = carType;
}
public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
this.id = id;
this.carNum = carNum;
this.brand = brand;
this.guidePrice = guidePrice;
this.produceTime = produceTime;
this.carType = carType;
}
public Car(){
}
}
- 第二步:Java程序
@Test
public void testInsertCarByPOJO(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//封装数据
Car car = new Car(null,"3333","bydnmd",30.0,"2020-11-11","新能源");
int count = sqlSession.insert("insertCar", car);//第一个放入的是CarMapper.xm里面的sqlid
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
- 第三步:SQL语句,
#{ }
里写的是POJO的属性名
<?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">
<!--namespace先随意写一个-->
<mapper namespace="car">
<!-- insert语句:id是这条SQL语句的唯一标识。这个id就代表了这条SQL语句 insert sql:保存一个汽车信息-->
<insert id="insertCar">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
经过测试得出结论:
严格意义上来说:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
-
写的是get方法名去掉get,然后将剩下的单词首字母小写,然后放进去
例如:getUsername() --> #{username} -
如果采用map集合传参,
#{}
里写的是map集合的key,如果key不存在不会报错,数据库表中会插入NULL
。 -
如果采用POJO传参,
#{}
里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写(例如:getAge对应的是#{age}
,getUserName对应的是#{userName}
,如果这样的get方法不存在会报错。
注意:其实传参数的时候有一个属性parameterType,这个属性用来指定传参的数据类型,不过这个属性是可以省略的
3.2 mybatis完成delete删除操作
需求:根据id删除数据
注意:当占位符只有一个的时候,
${}
里面的内容可以随便写。
- SQL语句写:
注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意思
<delete id="deleteById">
delete from t_car where id = #{SuiBianXie}
</delete>
- Java程序
@Test
public void testDeleteById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.delete("deleteById", 5);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
3.3 mybatis完成update更新操作
需求:根据id修改某条记录
- sql语句如下:
<update id="updateById">
update t_car set
car_num = #{carNum},
brand = #{brand},
guide_price = #{guidePrice},
produce_time = #{produceTime},
car_type = #{carType}
where id = #{id}
</update>
- 测试代码:
@Test
public void testUpdateById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//4L表示的是Long类型表示id:4 Long类型
Car car = new Car(4L,"9999","magua",30.3,"1999-11-10","旧能源");
int count = sqlSession.update("updateById", car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
当然,如果使用map传数据也是可以的
3.4 mybatis完成select查询一个的操作
select语句和其它语句不同的是:查询会有一个结果集对象:ResultSet。
需求:查询id为1的Car信息
- SQL语句如下:
<select id="selectById">
select * from t_car where id = #{id}
</select>
- 测试:
在JDBC中叫做ReusltSet,接下来是mybatis应该从ResultSet中取出数据,封装java对象
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//执行sql语句
Object car = sqlSession.selectOne("selectById", 1);
System.out.println(car);
sqlSession.commit();
sqlSession.close();
}
紧接着就报错了:
表示:已运行查询,但未找到映射语句" "的结果映射。很有可能既没有指定结果类型也没有指定结果映射
- 运行后之前的异常不再出现了,这说明添加了resultType属性之后,解决了之前的异常,可以看出resultType是不能省略的:
- 需要特别注意的是:select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成说明类型的java对象。你需要告诉mybatis。reultType通常写的是:全限定类名
<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
select * from t_car where id = #{id}
</select>
通过观察发现:只有id和brand是一致的,其他字段名和属性名对应不上,这是不是导致null的原因呢?
- carNum以及其他的这几个属性没有赋上值的原因是:
- car_num、guide_price、produce_time、car_type这是查询结果的列名。
这些列名和Car类中的属性名对不上。- car类的属性名:
carNum、guidePrice、produceTime、carType
那么这个问题该怎么解决?
我们尝试在sql语句中使用as关键字来给查询结果列名起别名试试:
<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as procedureTime,
car_type as carType
from t_car
where id = #{id}
</select>
通过测试得知,如果当查询结果的字段名和java类的属性名对应不上的话,可以采用as关键字起别名,当然还有其它解决方案,我们后面再看。
3.4.1 完成select查询所有
需求:查询所有的Car信息
- sql语句
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as procedureTime,
car_type as carType
from t_car
</select>
- 测试:
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
List<Object> cars = sqlSession.selectList("selectAll");
cars.forEach(car -> System.out.println(car));
sqlSession.commit();
sqlSession.close();
}
注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。selectList方法: mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合
3.5 SQLMapper映射文件中namespace的作用
在SQL Mapper配置文件中<mapper>
标签的namespace属性可以翻译为命名空间,这个命名空间主要是为了防止sqlId冲突的。
- 创建UserMapper.xml文件,代码如下:
<?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">
<!--namespace先随意写一个-->
<mapper namespace="aaaaaaaaaaaaaaa">
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as procedureTime,
car_type as carType
from t_car
</select>
</mapper>
不难看出,CarMapper.xml和UserMapper.xml文件中都有 id=“selectAll”
将UserMapper.xml配置到mybatis-config.xml文件中
- 测试
@Test
public void testNamespace(){
// 获取SqlSession对象
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执行SQL语句
List<Object> cars = sqlSession.selectList("selectCarAll");
// 输出结果
cars.forEach(car -> System.out.println(car));
}
报错:
【翻译】selectCarAll在Mapped Statements集合中不明确(请尝试使用包含名称空间的全名,或重命名其中一个条目)
【大致意思是】selectCarAll重名了,你要么在selectCarAll前添加一个名称空间,要有你改个其它名字。
- 代码修改:
@Test
public void testNameSpace(){
SqlSession sqlSession = SqlSessionUtil.openSession();
//List<Object> cars = sqlSession.selectList("selectAll");
//正确完整的写法:namespace.id
List<Object> cars = sqlSession.selectList("aaaaaaaaaaaaaaa.selectAll");
cars.forEach(car-> System.out.println(car));
sqlSession.commit();
sqlSession.close();
}