MyBatis入门
⼀、MyBatis概述
1.1 框架
在⽂献中看到的framework被翻译为框架Java常⽤框架:
SSM三⼤框架:Spring + SpringMVC + MyBatis
SpringBoot
SpringCloud
框架其实就是对通⽤代码的封装,提前写好了⼀堆接⼝和类,我们可以在做项⽬的时候直接引⼊这些接⼝和类(引⼊框架),基于这些现有的接⼝和类进⾏开发,可以⼤⼤提⾼开发效率。框架⼀般都以jar包的形式存在。(jar包中有class⽂件以及各种配置⽂件等。)
1.2 三层架构
表现层(UI):直接跟前端打交互(⼀是接收前端ajax请求,⼆是返回json数据给前端)
业务逻辑层(BLL):⼀是处理表现层转发过来的前端请求(也就是具体业务),⼆是将从持久层获取的数据返回到表现层。
数据访问层(DAL):直接操作数据库完成CRUD,并将获得的数据返回到上⼀层(也就是业务逻辑层)。
Java持久层框架:
MyBatis
Hibernate(实现了JPA规范)
Spring Data(实现了JPA规范)
…
1.3 JDBC不⾜
示例代码1:
// ......
// sql语句写死在java程序中
String sql = "insert into t_user(id,idCard,username,password,birth,gender,
email,city,street,zipcode,phone,grade) values(?,?,?,?,?,?,?,?,?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
// 繁琐的赋值:思考⼀下,这种有规律的代码能不能通过反射机制来做⾃动化。
ps.setString(1, "1");
ps.setString(2, "123456789");
ps.setString(3, "zhangsan");
ps.setString(4, "123456");
ps.setString(5, "1980-10-11");
ps.setString(6, "男");
ps.setString(7, "zhangsan@126.com");
ps.setString(8, "北京");
ps.setString(9, "⼤兴区凉⽔河⼆街");
ps.setString(10, "1000000");
ps.setString(11, "16398574152");
ps.setString(12, "A");
// 执⾏SQL
int count = ps.executeUpdate();
// ......
// ......
// sql语句写死在java程序中
String sql = "select id,idCard,username,password,birth,gender,email,city,s
treet,zipcode,phone,grade from t_user";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
List<User> userList = new ArrayList<>();
// 思考以下循环中的所有代码是否可以使⽤反射进⾏⾃动化封装。
while(rs.next()){
// 获取数据
String id = rs.getString("id");
String idCard = rs.getString("idCard");
String username = rs.getString("username");
String password = rs.getString("password");
String birth = rs.getString("birth");
String gender = rs.getString("gender");
String email = rs.getString("email");
String city = rs.getString("city");
String street = rs.getString("street");
String zipcode = rs.getString("zipcode");
String phone = rs.getString("phone");
String grade = rs.getString("grade");
// 创建对象
User user = new User();
// 给对象属性赋值
user.setId(id);
user.setIdCard(idCard);
user.setUsername(username);
user.setPassword(password);
user.setBirth(birth);
user.setGender(gender);
user.setEmail(email);
user.setCity(city);
user.setStreet(street);
user.setZipcode(zipcode);
user.setPhone(phone);
user.setGrade(grade);
// 添加到集合
userList.add(user);
}
// .....
JDBC不⾜:
SQL语句写死在Java程序中,不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。
1.4 了解MyBatis
MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。MyBatis在三层架构中负责持久层的,属于持久层框架。
MyBatis本是apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11⽉迁移到Github。iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。打开mybatis代码可以看到它的包结构中包含:ibatis
ORM:对象关系映射
O(Object):Java虚拟机中的Java对象
R(Relational):关系型数据库
M(Mapping):将Java虚拟机中的Java对象映射到数据库表中⼀⾏记录,或是将数据库表中⼀⾏记录映射成Java虚拟机中的⼀个Java对象。
ORM图示
MyBatis属于半⾃动化ORM框架。
Hibernate属于全⾃动化的ORM框架。
MyBatis框架特点:
⽀持定制化 SQL、存储过程、基本映射以及⾼级映射
避免了⼏乎所有的 JDBC 代码中⼿动设置参数以及获取结果集
⽀持XML开发,也⽀持注解式开发。【为了保证sql语句的灵活,所以mybatis⼤部分是采⽤XML⽅式开发。】
将接⼝和 Java 的 POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录
体积⼩好学:两个jar包,两个XML配置⽂件。
完全做到sql解耦合。
提供了基本映射标签。
提供了⾼级映射标签。
提供了XML标签,⽀持动态SQL的编写。
2.3 MyBatis⼊⻔程序开发步骤
创建数据库表
CREATE TABLE `my_batis_car` (
`id` bigint(20) DEFAULT NULL,
`brand` varchar(100) DEFAULT NULL comment '品牌',
`car_num` varchar(64) DEFAULT NULL comment '汽⻋编号',
`guide_price` decimal(12,2) DEFAULT NULL COMMENT '⼚家指导价【decimal类型,专⻔为财务数据准备的类型】',
`produce_time` datetime DEFAULT NULL comment '生产日期',
`car_type` varchar(64) DEFAULT NULL comment '汽⻋类型(燃油⻋、电⻋、氢能源)【varchar】'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
创建Project:建议创建Empty Project,设置Java版本以及编译版本等。
创建Module:普通的Maven Java模块
步骤1:打包⽅式:jar(不需要war,因为mybatis封装的是jdbc。)
<groupId>com.mybaties.demo</groupId>
<artifactId>mybaties-demo-01</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:在resources根⽬录下新建mybatis-config.xml配置⽂件(可以参考mybatis⼿册拷⻉)
<?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/xuexi"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--sql映射⽂件创建好之后,需要将该⽂件路径配置到这⾥-->
<mapper resource=""/>
</mappers>
</configuration>
注意1:mybatis核⼼配置⽂件的⽂件名不⼀定是mybatis-config.xml,可以是其它名字。
注意2:mybatis核⼼配置⽂件存放的位置也可以随意。这⾥选择放在resources根下,相当于放到了类的根路径下。
● 步骤4:在resources根⽬录下新建CarMapper.xml配置⽂件(可以参考mybatis⼿册拷⻉)
<?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 sql:保存⼀个汽⻋信息-->
<insert id="insertCar">
insert into my_batis_car
(id,car_num,brand,guide_price,produce_time,car_type)
values
(null,'123','联想L7',40.30,'2023-10-05','氢能源')
</insert>
</mapper>
注意1:sql语句最后结尾可以不写“;”
注意2:CarMapper.xml⽂件的名字不是固定的。可以使⽤其它名字。
注意3:CarMapper.xml⽂件的位置也是随意的。这⾥选择放在resources根下,相当于放到了类的根路径下。
注意4:将CarMapper.xml⽂件路径配置到mybatis-config.xml:
<mapper resource="mybatis.mapper/CarMapper.xml"/>
步骤5:编写MyBatisIntroductionTest代码
public static Integer insertData() {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactor对象
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sessionFactoryBuilder.build(resourceAsStream);
// 3.获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.执行Sql
int insertCarOne = sqlSession.insert("insertCarOne");
/*
* insertCarOne 为sql ID
*
* */
// 5. 提交(mybatis默认采⽤的事务管理器是JDBC,默认是不提交的,需要⼿动提交
sqlSession.commit();
// 6. 关闭资源
sqlSession.close();
return insertCarOne;
}
2.4 关于MyBatis核⼼配置⽂件的名字和路径详解
核⼼配置⽂件的名字是随意的,因为以下的代码:
// ⽂件名是出现在程序中的,⽂件名如果修改了,对应这⾥的java程序也改⼀下就⾏了。
InputStream is = Thread.currentThread().getContextClassLoader().getResource
AsStream("mybatis-config.xml");
核⼼配置⽂件必须放到resources下吗?放到D盘根⽬录下,可以吗?测试⼀下:
将mybatis-config.xml⽂件拷⻉⼀份放到D盘根下,然后编写以下程序:
public class MyBatisConfigFilePath {
public static void main(String[] args) throws Exception{
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSession
FactoryBuilder();
// 2. 创建SqlSessionFactory对象
// 这只是⼀个输⼊流,可以⾃⼰new。
InputStream is = new FileInputStream("D:/mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bui
ld(is);
// 3. 创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 执⾏sql
int count = sqlSession.insert("insertCar");
System.out.println("插⼊⼏条数据:" + count);
// 5. 提交(mybatis默认采⽤的事务管理器是JDBC,默认是不提交的,需要⼿动提
交。)
sqlSession.commit();
// 6. 关闭资源(只关闭是不会提交的)
sqlSession.close();
}
}
以上程序运⾏后,看到数据库表t_car中⼜新增⼀条数据,如下(成功了):
经过测试说明mybatis核⼼配置⽂件的名字是随意的,存放路径也是随意的。
虽然mybatis核⼼配置⽂件的名字不是固定的,但通常该⽂件的名字叫做:mybatis-config.xml
虽然mybatis核⼼配置⽂件的路径不是固定的,但通常该⽂件会存放到类路径当中,这样让项⽬的移植更
加健壮。
在mybatis中提供了⼀个类:Resources【org.apache.ibatis.io.Resources】,该类可以从类路径当
中获取资源,我们通常使⽤它来获取输⼊流InputStream,代码如下
●
// 这种⽅式只能从类路径当中获取资源,也就是说mybatis-config.xml⽂件需要在类路径下。
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
优化代码
public static Integer insertData() {
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactor对象
InputStream resourceAsStream =
Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = sessionFactoryBuilder.build(resourceAsStream);
// 3.获取SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执行Sql
int insertCarOne = sqlSession.insert("insertCarOne");
/*
* insertCarOne 为sql ID
*
* */
// 5. 提交(mybatis默认采⽤的事务管理器是JDBC,默认是不提交的,需要⼿动提交
sqlSession.commit();
return insertCarOne;
} catch (Exception e) {
e.printStackTrace();
return 0;
} finally {
// 6. 关闭资源
sqlSession.close();
}
}
2.6 引⼊JUnit
Unit是专⻔做单元测试的组件。
在实际开发中,单元测试⼀般是由我们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业务⽅法名(){}
// 测试⽤例
public class CarMapperTest{
// 测试⽅法
@Test
public void testInsert(){}
@Test
public void testUpdate(){}
}
第三步:可以在类上执⾏,也可以在⽅法上执⾏
在类上执⾏时,该类中所有的测试⽅法都会执⾏。
在⽅法上执⾏时,只执⾏当前的测试⽅法。
编写⼀个测试⽤例,来测试insertCar业务
2.7 引⼊⽇志框架logback
引⼊⽇志框架的⽬的是为了看清楚mybatis执⾏的具体sql。
启⽤标准⽇志组件,只需要在mybatis-config.xml⽂件中添加以下配置:【可参考mybatis⼿册】
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
标准⽇志也可以⽤,但是配置不够灵活,可以集成其他的⽇志组件,例如:log4j,logback等。
logback是⽬前⽇志框架中性能较好的,较流⾏的,所以我们选它。
引⼊logback的步骤:
第⼀步:引⼊logback相关依赖
<!--引入logback的依赖,这个日志框架实现了slf4j规范-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
第⼆步:引⼊logback相关配置⽂件(⽂件名叫做logback.xml或logback-test.xml,放到类路径当中)
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义⽇志⽂件的存储地址-->
<property name="LOG_HOME" value="/home"/>
<!-- 控制台输出 -->
<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 %logge
r{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.PatternLayoutEncode
r">
<!--格式化输出:%d表示⽇期,%thread表示线程名,%-5level:级别从左显示5
个字符宽度%msg:⽇志消息,%n是换⾏符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logge
r{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>
再次执⾏单元测试⽅法testInsertCar,查看控制台是否有sql语句输出
2.8 MyBatis⼯具类SqlSessionUtil的封装
每⼀次获取SqlSession对象代码太繁琐,封装⼀个⼯具类
package com.mybaties.demo.util;
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 SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
/**
* 类加载时初始化sqlSessionFactory对象
*/
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 每调⽤⼀次openSession()可获取⼀个新的会话,该会话⽀持⾃动提交。
*
* @return 新的会话对象
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession(true);
}
}
使用:
@Test
public void testInsertCar(){
SqlSession sqlSession = SqlSessionUtil.openSession();
// 执⾏SQL
int count = sqlSession.insert("insertCar");
System.out.println("插⼊了⼏条记录:" + count);
sqlSession.close();
}