1 Mybatis概述
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
mybatis技术曾经做过“更名”,早期这个团队将技术框架称为“iBatis",mybatis研发团队”跳槽“google,从google跳”githup",将之前“ibatis"改为”mybaits"
mybatis中文网站:https://mybatis.net.cn/
2 mybatis框架技术定位
SSM框架与三层架构对应关系: Java提供JDBC,Spring提供SpringJdbc,mybatis
3 Mybatis的优势:传统jdbc存在的问题
JDBC操作步骤:
1、注册驱动:Class.forName(“com.mysql.jdbc.Driver”)
2、获取连接:Connection connection=DriverManager.getConnection(url,username,password);
3、获取预处理对象:PreparedStatement pstm=connection.PreparedStatement(“sql”);
4、执行sql操作:Result result=pstm.ExecuteQuery();/pstm.ExecuteReader().
5、封装结果集:While(result.next()){……}
6、释放资源:Result.close(); Pstm.close(); Connection.close();
分析:如果我们要在mysql中查询一个内容应该怎样操作才能得到结果?在mysql中我们只需要编写出相应的sql语句即可,但是在jdbc中除了编写sql语句外还要做很多额外的工作才能得到结果。能不能只编写sql语句其余工作不再去关注呢?这就是mybatis的作用,我们唯一要做的就是思考怎样去编写高效的sql语句。
4 Mybatisの入门案例
创建maven控制台程序,按照以下步骤完成mybatis开发环境搭建工作:
- pom.xml 导入mybatis依赖
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 数据库驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
- 项目中引入数据库配置文件:db.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/bookstore?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=root
- 添加mybatis核心配置文件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>
<!--
加载数据库配置信息的文件:db.properties
properties:读取classpath下面的配置文件,所以不用写classpath
-->
<properties resource="db.properties"/>
<!--
配置数据库连接信息:从配置文件读取对应的key的值
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
</configuration>
mybatis-confg.xml是mybatis的核心配置文件,该文件中要配置哪些东西呢:即基本的sql语句执行环境需要的就是数据驱动类型、数据库服务器地址、数据库服务器登录账号及密码。
- 测试代码
-
public class Tester { @Test public void test01()throws Exception{ //1.读取配置文件,构建mybatis核心执行对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //通过工厂获取数据库连接对象 SqlSession session = sqlSessionFactory.openSession(); System.out.println(session); } }
5 Mybatis的CRUD操作
准备工作
确定要操作的数据库和数据表后,按照持久层dao开发流程,完成代码准备:
创建Notice实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Notice {
private Integer id;
private String title;
private String content;
private Integer employeeId;
//jdk1.8新增日期格式:LocalDateTime对象中包含:年月日时分秒组成
private LocalDateTime createTime;
}
为表创建对应的dao接口:NoticeDao.java
/**
* mybatis接口不再提供实现类,取而代之使用xml配置文件完成代码执行,
* xml文件主要作用:写sql,成为sql映射文件
* sql映射文件要求:文件名必须和接口同名,单词一模一样;文件存放位置和接口所在包一样
*/
public interface NoticeDao {
/**
* 查询所有的帖子信息
*/
List<Notice> selectAll();
}
为NoticeDao接口创建SQL映射文件:NoticeDao.xml
mybatis中规定dao只是用来声明方法的接口类,接口中方法的实现由mapper.xml映射文wrh来实现(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:sql映射文件.作用:配置sql语句
namespace:命名空间,理解包 设置当前sql映射文件是对应哪个接口文件,填写接口包名.类名
-->
<mapper namespace="com.woniu.dao.NoticeDao">
<select id="" resultType="">
</select>
</mapper>
注意:mapper.xml映射文件须和dao接口在同一包中且名称相同。创建好的文件目录如下图所示:
案例一:查询所有公告信息
修改NoticeDao.xml映射文件:为查询方法配置对应的sql语句
注意:使用mapper代理开发时不用为dao接口编写实现类,但是在mapper.xml文件中的mapper节点中的namespace必须指定与同名接口类的全限定名
<!--
id:指定sql为接口里面哪个方法提供的。id设置方法:方法名
resultType:方法返回值类型,如果方法返回值类型是集合,只需要填写集合泛型类型即可
指定类型时:必须使用全路径:包名.类名
-->
<select id="selectAll" resultType="com.woniu.entity.Notice">
SELECT id,title,content,employeeId,createtime from wy_notice
</select>
属性说明:
id: 设置要实现的方法名称
resultType: 方法返回值类型,如果返回值是集合类型,写集合的泛型类型。
鉴于之前使用JdbcTemplate执行sql的经验,我们可以这么理解resultType:
resultType提供类型用于完成查询结果与实体类映射关系,默认情况:entity定义属性时属性名和表列名单词一样的。
注意:使用mapper代理开发时mapper.xml中sql节点的id值必须与dao接口中的方法名称一致
resultType属性的值必须与dao接口中方法的返回值类型一致
核心配置文件中注册Sql映射文件:NoticeDao.xml
每一个mapper.xml映射文件要能正确被程序解析到,还要在mybatis核心配置文件中进行注册
<mappers>
<mapper resource="com/woniu/dao/NoticeDao.xml"/>
</mappers>
resource读取的是classpath下的文件url地址,所以此处写的是xml的路径,不是接口的包名
常见异常:
如果没有在核心配置文件中“注册”sql映射文件,执行代码时通常会提示以下异常:
测试代码
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象
SqlSession session = sqlSessionFactory.openSession();
System.out.println(session);
//执行sql语句了 重点,mapper其实就是依靠jdk代理为接口生成实现类
NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);
//com.sun.proxy.$Proxy6 基于jdk代理,获取接口对应的实现类
System.out.println(noticeDaoImpl.getClass());
List<Notice> notices = noticeDaoImpl.selectAll();
notices.forEach(System.out::println);
}
观察以上输出结果,我们不难发现,mybatis的底层其实是利用JDK代理完成了dao接口对应实现类的动态生成。我们之前学过JDK代理的知识,我们可以这么理解
所以,在mybatis 框架中它将“通用且重复”的sql执行过程在程序运行过程中,动态增强到xml配置的sql语句前后。简化了程序员持久层开发工作的负担和繁复。
案例二:DML操作の添加公告信息
修改NoticeDao.xml映射文件:为insert方法配置对应的sql语句
注意:使用mapper代理开发时不用为dao接口编写实现类,但是在mapper.xml文件中的mapper节点中的namespace必须指定与同名接口类的全限定名
<insert id="insert">
insert wy_notice values(null,'测试数据','测试数据内容',1,now())
</insert>
属性说明:
id: 设置要实现的方法名称
insert配置新增语句,该标签一般只需要配置id即可
因为insert没有查询结果集,所以insert标签也就没有resultType属性了
测试代码
@Test
public void testInsert()throws IOException{
//1.获取SqlSession
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//2.基于jdk代理,获取要测试接口对应的实现类
NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);
int insert = noticeDaoImpl.insert(null);
System.out.println("insert语句执行后,受影响的行数:" + insert);
}
添加代码执行成功后,数据库数据并没有新增的问题:
测试结果暗示的意思是数据添加成功,我们去刷新mysql数据库,观察发现并没有出现”测试数据“这条新增数据,why???
真相是:mybatis在openSession()时,默认开启了事务手动提交模式,所以在没有代码明确写明“commit()"的情况下,程序操作的结果不会物理更新到数据表中。
解决方案如下:
方案一:手动提交事务
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
SqlSession session = sqlSessionFactory.openSession();
//sql执行:获取UserDao接口的代理对象,
NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);
int i = noticeDaoImpl.insert(null);
System.out.println("新增方法执行结果是:" + i);
//提交事务
session.commit();
//释放资源
session.close();
}
方案二:开启事务自动提交模式
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
SqlSession session = sqlSessionFactory.openSession(true);
//sql执行:获取UserDao接口的代理对象,
NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);
int i = noticeDaoImpl.insert(null);
System.out.println("新增方法执行结果是:" + i);
//释放资源
session.close();
}
扩展补充:SqlSession释放资源的问题
SqlSession使用完毕后,也是要释放数据库资源的,所以此处如果想不写“session.close()“,可以借助JDK1.8中try-catch的新语法,代码如下所示:
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
try(SqlSession session = sqlSessionFactory.openSession(true);) {
//sql执行:获取UserDao接口的代理对象,
NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);
int i = noticeDaoImpl.insert(null);
System.out.println("新增方法执行结果是:" + i);
}
}
6 日志框架 logback
模仿日志输出,日志将程序执行过程,执行了什么sql,带入什么参数,执行的是什么结果,执行出现问题具体描述信息…
作用:日志框架增强程序员跟踪程序执行过程,对于发生一些程序问题,更好进行定位、分析。
日志框架选择:Springboot内置日志框架:logback
logback使用步骤
-
pom.xml导入logback的依赖:整个日志框架需要导入三个依赖:logback-classic、logback-core、slf4j-api
-
<!-- 日志框架 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency>
-
项目引入自己的日志配置文件:logback.xml
-
<configuration debug="false"> <!-- appender配置输出的位置和输出日志格式 CONSOLE:控制台 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%5p | %-40.40logger{39} :%m%n</pattern> <charset>utf8</charset> </encoder> </appender> <!-- 了解日志信息输出的级别:TRACE < DEBUG < INFO < WARN < ERROR 配置哪些日志在控制台输出,可以通过设置不同的日志级别控制 日志级别的输出原则:设置级别时,会输出当前设置级别的日志以及比当前更高级别的日志 --> <!-- logger:特定设置,属性 name:特定配置针对的包名,酌情修改 level:DEBUG additivity:覆盖默认配置 --> <logger name="com.woniu" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> </logger> <!-- root:基础配置,level:info --> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> </configuration>
-
自己使用log对象输出日志信息
-
@Slf4j //引入日志管理器 public class xxx{ @Test public void test00(){ String str="helloworld"; //观察控制台能否输出对应的信息:信息是否可以输出与logback.xml配置的级别有关系 log.trace("trace str{}",str); log.debug("debug str{}",str); log.info("info str{}",str); log.warn("warn str{}",str); log.error("error str{}",str); } }
按照步骤配置好日志框架后,重新启动上面案例的测试代码,就可以在控制台看到sql的输出信息了。
7 mybatis中#{}和${}设置sql参数对比
案例:根据id查询用户信息
修改UserDao.xml中配置:使用${}带入参数
<select id="selectById" resultType="com.woniu.entity.Users">
select * from wy_employee where id=${id}
</select>
执行测试方法
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
try(SqlSession session = sqlSessionFactory.openSession(true);) {
System.out.println(session);
//sql执行:获取UserDao接口的代理对象,
UsersDao usersDaoImpl = session.getMapper(UsersDao.class);
System.out.println(usersDaoImpl.getClass());
//调用方法,获取执行结果
List<Users> list = usersDaoImpl.selectAll("id");
list.forEach(System.out::println);
}
}
修改UserDao.xml中配置:使用#{}带入参数
<select id="selectById" resultType="com.woniu.entity.Users">
select * from wy_employee where id=#{id}
</select>
再次执行同一个测试方法,观察两次测试的日志输出结果:
由上图得出以下结论:
#{}与${}的区别:
#{}:使用?占位符,即是将sql语句编译好后再取值,能够有效防止sql注入,#{}取的是属性中的值
${}:是sql后面直接拼接参数值,即取值后再编译语句,不能防止注入。
适用场景:${}方式一般用于传入数据库对象,例如传入表名或者列名,就只能使用${}带入参数。
在实际使用中能使用#{}就不用${},
案例2:根据指定列名完成查询结果的排序
-
UsersDao.java中定义方法
List<Users> selectAll(String name);
-
UsersDao.xml配置sql
select * from wy_employee order by ${name} desc -
测试代码
@Test
public void test01()throws Exception{
//1.读取配置文件,构建mybatis核心执行对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
try(SqlSession session = sqlSessionFactory.openSession(true);) {
System.out.println(session);
//sql执行:获取UserDao接口的代理对象,
UsersDao usersDaoImpl = session.getMapper(UsersDao.class);
System.out.println(usersDaoImpl.getClass());
//调用方法,获取执行结果
List<Users> list = usersDaoImpl.selectAll("id");
list.forEach(System.out::println);
}
}
- 将UserDao.xml中sql修改成#{}再次执行单元测试
<select id="selectAll" resultType="com.woniu.entity.Users">
select * from wy_employee order by #{name} desc
</select>
观察执行结果可以发现:#{}带入参数时,查询结果没有排序。只有使用${}带入参数时,才有排序的效果
8 #{}带入参数的写法
#{}带入参数时,使用什么名称来引用值,分为三种场景区别:
1 方法有且只有一个参数,并参数是基本类型或String,#{随便写}
形如:Users selectById(Long id);
配置sql:select * from wy_employee where id=#{随便写,一般见词知意形参名}
2 方法有且只有一个参数,并参数是对象,#{对象的属性名}
形如:insert(user u) update(User u)
3 方法N个参数 #{参数} 借助注解@Param给参数设置引用名
形如:
int update(@Param("pkId") Long id, @Param("pwd") String password,@Param("username") String realName);
xml文件:update wy_employee set password=#{pwd},realname=#{username} where id=#{pkId}
案例:新增公告信息
- NoticeDao.java定义新增方法
-
int insert(Notice notice);
- NoticeDao.xml配置insert语句
-
<insert id="insert"> insert wy_notice values(null,#{title},#{content},#{userId},#{createTime}) </insert>
- MyTester.java测试代码
-
@Test public void test02()throws Exception{ //1 指定mybatis开发环境基于哪个配置文件来使用resource基于classpath String resource = "mybatis-config.xml"; //2 基于配置文件构建IO输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //SqlSession本质就是Connection SqlSessionFactory是连接池 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //执行sql语句,验证执行结果,连接池获取连接对象时,其实需要指定事务提交方式,如果不指定默认不是自动提交模式 try (SqlSession session = sqlSessionFactory.openSession(true)) { //获取dao实现类对象,mybatis依据JDK代理模式动态获取实现类型 NoticeDao implProxy = session.getMapper(NoticeDao.class); int i = implProxy.insert( Notice.builder() .title("aaaaaaaaaaaa") .content("bbbbbbbbbbbbbbbbbbbbbbb") .userId(1) .createTime(LocalDateTime.now()) .build() ); System.out.println("本次新增的数据行数:"+i); }
- 跟踪控制台输出的日志结果
常见坑点
sql映射文件没有在核心配置文件中注册时:
Sql映射文件和所实现的接口文件不在同一个目录
如何确认接口文件和sql映射文件是否在一个目录呢?可以通过mvn:compile编译项目后,观察target中的classes目录。同一目录的现象如下所示:
UsersDao.java和UsersDao.xml“紧紧挨在一起“
i