01.MyBatis框架介绍
一、企业级应用
企业级应用是指那些为商业组织、大型企业而创建并部署的解决方案及应用。这些大型企业级应用的结构复杂,涉及的外部资源众多、事务密集、数据量大、用户数多,有较强的安全性考虑。
企业级开发主要是针对企业级应用的开发。作为企业级应用,其不但要有强大的功能,还要能够满足未来业务需求的变化,易于升级和维护。
在规模较大的Java企业级应用开发中,直接使用JDBC API进行数据访问层开发存在诸多不便,所以我们要使用MyBatis框架简化数据访问层的开发。
二、框架的概念
框架,即Framework。该词最早出现在建筑领域,指的是在建造房屋前期构建的建筑骨架。
在编程领域,框架就是应用程序的骨架,开发人员可以在这个骨架上加入自己的东西,搭建出符合自己需求的应用系统。
其实就是某种应用的半成品,是一组组件,但是这个东西复用性特别强,可以让广大程序开发人员完成自己的系统。
简而言之,框架就是可以被应用开发者定制的应用骨架。而且,框架一般是成熟的,不断升级的软件。
框架的优势
1.使用JSP+Servlet技术进行JavaEE应用的开发有两个弊端:
- 软件应用和系统可维护性差
- 代码重用性低
2.相比于使用JSP+Servlet技术进行软件开发,使用框架有以下优势:
- 提高开发效率
- 提高代码规范性和可维护性
- 提高软件性能
三、主流框架技术简介
目前JavaEE企业级开发的三大主流框架是SSM(Spring,SpringMVC,Mybatis)。
Spring框架
Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的,其主要优势之一就是分层架构。Spring提供了更完善的开发环境,可以为POJO(Plain Ordinary Java Object,普通Java对象)对象提供企业级的服务。
Spring MVC框架
Spring MVC是一个Web开发框架,可以将它理解为Servlet。在MVC模式中,Spring MVC作为控制器(Controller)用于实现模型与视图的数据交互,是结构最清晰的。Spring MVC框架采用松耦合、可插拔的组件结构,具有高度可配置性,与其他的MVC框架相比,具有更强的扩展性和灵活性。
MyBatis框架
MyBatis是Apache的一个开源项目iBatis,2010年这个项目由Apache Software Foundation迁移到了Google Code,并且改名为MyBatis,2013年11月MyBatis又被迁移到Github。MyBatis是一个优秀的持久层框架,它可以在实体类和SQL语句之间建立映射关系,是一种半自动化的ORM(Object/Relation Mapping,即对象关系映射)实现。MyBatis封装性要低于Hibernate,但它性能优越、简单易学,在互联网应用的开发中被广泛使用。
四、MyBatis框架
(一)传统JDBC的劣势
1.影响系统性能:数据库连接创建、释放频繁会造成系统资源浪费,从而影响系统性能。
2.不易维护:
(1)SQL语句在代码中硬编码,造成代码不易维护。在实际应用的开发中,SQL变化的可能性较大。在传统JDBC编程中,SQL变动需要改变Java代码,不易维护。
(2)使用PreparedStatement向占位符传参数存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改SQL需要修改Java代码,不易维护。
(3)JDBC对结果集解析存在硬编码(查询列名),SQL变化会导致解析的Java代码变化,不易维护。
(二)MyBatis概述
1.MyBatis框架是一个ORM(Object Relation Mapping,即对象关系映射)框架。
2.所谓的ORM就是一种为了解决面向对象与关系型数据库中数据类型不匹配的技术,它通过描述Java对象与数据库表之间的映射关系,自动将Java应用程序中的对象持久化到关系型数据库的表中。
ORM 把数据库映射为对象:
- 数据表(table)→ 类(class)
- 数据行(record,记录)→ 对象(object)
- 字段(field)→ 对象的属性(attribute)
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。
3.针对上面提到的传统JDBC编程的劣势,MyBatis提供了以下解决方案:
问题一:数据库连接创建、释放频繁会造成系统资源浪费,从而影响系统性能。
解决方案:在mybatis-config.xml中配置数据连接池,使用连接池管理数据库连接。
问题二:SQL语句在代码中硬编码,造成代码不易维护。在实际应用的开发中,SQL变化的可能性较大。在传统JDBC编程中,SQL变动需要改变Java代码,不易维护。
解决方案:MyBatis将SQL语句配置在MyBatis的映射文件中,实现了SQL语句与Java代码的分离。
问题三:使用PreparedStatement向占位符传参数存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改SQL需要修改Java代码,不易维护。
解决方案:MyBatis自动将Java对象映射至SQL语句,通过Statement中的parameterType定义输入参数的类型。
问题四:JDBC对结果集解析存在硬编码(查询列名),SQL变化会导致解析的Java代码变化,不易维护。
解决方案:MyBatis自动将SQL执行结果映射至Java对象,通过Statement中的resultType定义输出结果的类型。
(三)MyBatis环境搭建
1.使用步骤
在项目中使用MyBatis框架可以按照以下几个步骤进行:
(1)创建项目,添加所需要的jar文件,可以下载jar后添加,也可以添加依赖引用。
(2)编写MyBatis框架的核心配置文件。
(3)创建实体类。
(4)创建Mapper接口。
(5)创建SQL映射文件。
(6)编写业务逻辑代码。
2.开始实践
(1)数据准备
参见JavaWeb通过JDBC连接MySQL数据库 中“0、数据准备”部分。
(2)创建项目
使用IDEA创建一个JavaWeb项目。项目名称填写“MyBatisTest”,构建系统选择【Maven】,组ID填写“com.sdbi”。
添加“Web应用程序”框架支持。
运行项目测试是否成功。
运行Tomcat,浏览器地址栏输入http://localhost:8080/MyBatisTest/,看到成功页面,说明项目创建成功。
(3)引入相关依赖
需要导入MySQL驱动包,MyBatis核心包。
Maven方式:
在项目的pom.xml文件中添加如下代码:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>com.sdbi</groupId> 8 <artifactId>MyBatisTest</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <properties> 12 <maven.compiler.source>8</maven.compiler.source> 13 <maven.compiler.target>8</maven.compiler.target> 14 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 15 </properties> 16 17 <dependencies> 18 <dependency> 19 <groupId>mysql</groupId> 20 <artifactId>mysql-connector-java</artifactId> 21 <version>5.1.49</version> 22 </dependency> 23 <dependency> 24 <groupId>org.mybatis</groupId> 25 <artifactId>mybatis</artifactId> 26 <version>3.5.9</version> 27 </dependency> 28 </dependencies> 29 </project>
上述代码中,第18~22行是MySQL驱动包,第23~27行是MyBatis核心包。
保存pom.xml文件后,点击右侧悬浮的同步按钮,如果同步缓慢或者不成功,我们可以使用本地Maven。
在<mirrors>节点内,添加如下代码,使用阿里云的Maven。
1 <mirror> 2 <id>aliyunmaven</id> 3 <mirrorOf>*</mirrorOf> 4 <name>阿里云公共仓库</name> 5 <url>https://maven.aliyun.com/repository/public</url> 6 </mirror>
到IDEA设置中,找到【构建、执行、部署】->【构建工具】->【Maven】,将【Maven主路径】修改为本机安装的Maven路径。
我们使用Maven方式添加jar包,在发布项目之前一定要在【项目结构】--【工件】中将Maven方式导入的包(右侧)添加到【输出根】(左侧)中。
添加jar包方式:
下载mysql驱动包和MyBatis包(https://repo.maven.apache.org/maven2/org/mybatis/mybatis/3.5.9/mybatis-3.5.9.jar)
在web文件夹下新建lib文件夹,将jar包复制粘贴到lib文件夹下,
右键jar包,选择【添加为库】
到【文件】->【项目结构】中查看一下,已经添加成功。
(4)创建数据库连接配置文件
在src/main/resources目录下创建db.properties文件,在该文件中配置数据库连接的参数。
mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/db_test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false mysql.username=root mysql.password=1234
(5)创建MyBatis核心配置文件
在src/main/resources目录下创建mybatis-config.xml文件,该文件主要用于项目的环境配置。
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 4 <configuration> 5 <!-- 环境配置 --> 6 <!-- 加载类路径下的属性文件 --> 7 <properties resource="db.properties"/> 8 <environments default="development"> 9 <environment id="development"> 10 <transactionManager type="JDBC"/> 11 <!-- 数据库连接配置,db.properties文件中的内容 --> 12 <dataSource type="POOLED"> 13 <property name="driver" value="${mysql.driver}"/> 14 <property name="url" value="${mysql.url}"/> 15 <property name="username" value="${mysql.username}"/> 16 <property name="password" value="${mysql.password}"/> 17 </dataSource> 18 </environment> 19 </environments> 20 </configuration>
至此,MyBatis的开发环境就搭建完成了。
(四)第一个MyBatis程序
我们来试一下刚刚配置好的MyBatis开发环境。
1.创建POJO实体
在src/maim/java目录下创建com.sdbi.pojo包,在该包下创建User类。
package com.sdbi.pojo; public class User { private int id; private String name; private String password; private String email; private String birthday; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", birthday='" + birthday + '\'' + '}'; } }
2.创建映射文件UserMapper.xml
在src/maim/resources目录下创建一个mapper文件夹,在mapper文件夹下创建映射文件UserMapper.xml。映射文件通常用POJO实体类名+Mapper命名。
注意:第5行<mapper>标签中一定要添加namespace属性。我们这里没有使用Mapper代理接口,所以namespace是我们定义的POJO类。
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <!-- mapper为映射根节点 --> 5 <mapper namespace="com.sdbi.pojo.User"> 6 <!-- id是接口中的“方法名”,parameterType是传入参数的数据类型,resultType是返回的实体类名(包名.类名)--> 7 <select id="findByName" parameterType="String" resultType="com.sdbi.pojo.User"> 8 select * 9 from tb_users 10 where name = #{name} 11 </select> 12 <select id="findById" parameterType="Integer" resultType="com.sdbi.pojo.User"> 13 select * 14 from tb_users 15 where id = #{id} 16 </select> 17 </mapper>
第7~11行定义了通过name查询的<select>标签,第7~11行定义了通过name查询的<select>标签。
3.修改mybatis-config.xml配置文件
在mybatis-config.xml配置文件的第19行代码后,添加UserMapper.xml映射文件路径的配置,用于将UserMapper.xml映射文件加载到程序中。
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 4 <configuration> 5 <!-- 环境配置 --> 6 <!-- 加载类路径下的属性文件 --> 7 <properties resource="db.properties"/> 8 <environments default="development"> 9 <environment id="development"> 10 <transactionManager type="JDBC"/> 11 <!-- 数据库连接配置,db.properties文件中的内容 --> 12 <dataSource type="POOLED"> 13 <property name="driver" value="${mysql.driver}"/> 14 <property name="url" value="${mysql.url}"/> 15 <property name="username" value="${mysql.username}"/> 16 <property name="password" value="${mysql.password}"/> 17 </dataSource> 18 </environment> 19 </environments> 20 21 <mappers> 22 <mapper resource="mapper/UserMapper.xml"/> 23 </mappers> 24 </configuration>
4.编写Servlet类
先添加servlet-api.jar依赖
可以通过添加依赖的方式:
1 <dependency> 2 <groupId>javax.servlet</groupId> 3 <artifactId>javax.servlet-api</artifactId> 4 <version>4.0.1</version> 5 <scope>provided</scope> 6 </dependency>
也可以导入jar包:
编写Servlet类,命名为UserServlet。一定注意要添加@WebServlet("/UserServlet")。1 package com.sdbi.servlet; 2 3 import com.sdbi.pojo.User; 4 import org.apache.ibatis.io.Resources; 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 8 9 import javax.servlet.ServletException; 10 import javax.servlet.annotation.WebServlet; 11 import javax.servlet.http.HttpServlet; 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 import java.io.IOException; 15 import java.io.Reader; 16 17 @WebServlet("/UserServlet") 18 public class UserServlet extends HttpServlet { 19 @Override 20 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 21 System.out.println("doGet()...start"); 22 resp.setContentType("text/html;charset=utf-8"); 23 String resouces = "mybatis-config.xml"; 24 // 创建流 25 Reader reader = null; 26 27 try { 28 // 读取mybatis-config.xml文件内容到reader对象中 29 reader = Resources.getResourceAsReader(resouces); 30 // 初始化MyBatis数据源,创建SqlSessionFactory类的实例 31 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 32 // 创建SqlSession类的实例 33 SqlSession session = sqlSessionFactory.openSession(); 34 // 传入参数,执行findByName查询,返回结果User 35 User user = session.selectOne("findByName", "zhangsan"); 36 // User user = session.selectOne("findById", 2); 37 // 输出结果 38 if (user != null) { 39 System.out.println(user.toString()); 40 resp.getWriter().println(user.toString()); 41 } else { 42 System.out.println("not found."); 43 resp.getWriter().println("not found."); 44 } 45 // 关闭SqlSession对象 46 session.close(); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 System.out.println("doGet()...end"); 51 } 52 }
第29行用于读取mybatis-config.xml文件内容到reader对象中;
第31~33行用于创建SqlSessionFactory类的实例,并通过SqlSessionFactory类的实例创建SqlSession类的实例;
第35行调用selectOne()方法,查询name为“zhangsan”的用户信息,并将查询结果返回给User对象。
或者,第36行调用selectOne()方法,查询id为2的用户信息,并将查询结果返回给User对象。
运行结果如下:
错误解决
错误现象:
如果使用Maven通过依赖添加MyBatis的jar包,在运行时会出现如下错误。
15-Mar-2023 15:42:52.022 严重 [http-nio-8080-exec-4] org.apache.catalina.core.StandardWrapperValve.invoke 在路径为/MyBatisTest的上下文中,Servlet[com.sdbi.servlet.UserServlet]的Servlet.service()引发了具有根本原因的异常Servlet执行抛出一个异常
java.lang.ClassNotFoundException: org.apache.ibatis.io.Resources
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1420)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1228)
at com.sdbi.servlet.UserServlet.doGet(UserServlet.java:31)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:656)
原因:没有在设置中将项目设置为输出目录。
在工件设置中,将项目设置为输出目录。
(五)IDEA中创建XML模板
注意:对于经常使用的XML文件我们可以在IDEA中创建模板。
1.【文件】->【设置】->【编辑器】->【文件和代码模板】
2.填写模板名称为“mybatis-mapper.xml”,扩展名为“xml”,内容填写基本的代码部分。
模板代码:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace=""> 5 6 </mapper>
3.使用模板
(六)MyBatis的面向接口编程
我们还可以使用MyBatis的面向接口编程。
MyBatis的面向接口编程的作用就是将所有的数据库操作交由mapper的配置来完成,而不需要人工的在dao层写数据库操作的代码。
我们先来定义Dao层接口,该接口的名字可以是以Dao结尾,也可以是以Mapper结尾,例如,UserDao(包名com.sdbi.dao),UserMapper(包名com.sdbi.mapper)。
1.新建com.sdbi.mapper包,在包里新建一个接口UserMapper,代码如下:
package com.sdbi.mapper; import com.sdbi.pojo.User; public interface UserMapper { public User findByName(String name); public User findById(Integer id); }
2.修改UserMapper.xml第5行的namespace属性,改为刚刚定义的接口:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <!-- mapper为映射根节点 --> 5 <mapper namespace="com.sdbi.mapper.UserMapper"> 6 <!-- id是接口中的“方法名”,parameterType是传入参数的数据类型,resultType是返回的实体类名(包名.类名)--> 7 <select id="findByName" parameterType="String" resultType="com.sdbi.pojo.User"> 8 select * 9 from tb_users 10 where name = #{name} 11 </select> 12 <select id="findById" parameterType="Integer" resultType="com.sdbi.pojo.User"> 13 select * 14 from tb_users 15 where id = #{id} 16 </select> 17 </mapper>
3.修改UserServlet类的第35~38行:
1 package com.sdbi.servlet; 2 3 import com.sdbi.mapper.UserMapper; 4 import com.sdbi.pojo.User; 5 import org.apache.ibatis.io.Resources; 6 import org.apache.ibatis.session.SqlSession; 7 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 9 10 import javax.servlet.ServletException; 11 import javax.servlet.annotation.WebServlet; 12 import javax.servlet.http.HttpServlet; 13 import javax.servlet.http.HttpServletRequest; 14 import javax.servlet.http.HttpServletResponse; 15 import java.io.IOException; 16 import java.io.Reader; 17 18 @WebServlet("/UserServlet") 19 public class UserServlet extends HttpServlet { 20 @Override 21 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 22 System.out.println("doGet()...start"); 23 resp.setContentType("text/html;charset=utf-8"); 24 String resouces = "mybatis-config.xml"; 25 // 创建流 26 Reader reader = null; 27 28 try { 29 // 读取mybatis-config.xml文件内容到reader对象中 30 reader = Resources.getResourceAsReader(resouces); 31 // 初始化MyBatis数据源,创建SqlSessionFactory类的实例 32 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 33 // 创建SqlSession类的实例 34 SqlSession session = sqlSessionFactory.openSession(); 35 // 调用SqlSession的getMapper方法,获取指定接口的实现类对象 36 UserMapper userMapper = session.getMapper(UserMapper.class); 37 // 传入参数,调用实现类对象的方法,返回结果User 38 User user = userMapper.findById(3); 39 // 输出结果 40 if (user != null) { 41 System.out.println(user.toString()); 42 resp.getWriter().println(user.toString()); 43 } else { 44 System.out.println("not found."); 45 resp.getWriter().println("not found."); 46 } 47 // 关闭SqlSession对象 48 session.close(); 49 } catch (Exception e) { 50 e.printStackTrace(); 51 } 52 System.out.println("doGet()...end"); 53 } 54 }
MyBatis框架抛开了Dao的实现类,直接定位到映射文件mapper中的相应SQL语句,对DB进行操作。这种对Dao的实现方式称为Mapper的动态代理方式。
Mapper动态代理方式无需实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代理实现的。
相关接口和映射文件之间的规则:
(1)在mapper.xml中将namespace设置为对应的mapper.java(Dao接口)的全限定名。
(2)将mapper.java接口的方法名和mapper.xml中statement的id保持一致。
(3)将mapper.java接口的方法输入参数类型和mapper.xml中statement的parameterType保持一致。
(4)将mapper.java接口的方法输出结果类型和mapper.xml中statement的resultType保持一致。
Dao 接口不需要实现类
MyBatis在采用面向接口编程时,采用实体+接口+映射文件的方式。其中接口是不需要实现类的。
因为Mybatis提供了Mapper接口的代理对象(MyBatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成Dao的实现,session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理class的instance。)
在执行Mapper接口方法时,实际执行的是MyBatis的代理对象,代理对象在 invoke 方法内获取 Mapper接口类全名+方法全名 作为statement的id,然后通过id去statement匹配注册的SQL,然后使用 SqlSession 执行这个 SQL。所以,这也解释了为什么Mybatis映射文件需要 namespace 和 id ,前者是类全名,后者是方法名。
(七)MyBatis工作原理
MyBatis框架在操作数据库时,大体经过了8个步骤。下面结合MyBatis工作原理图对每一步流程进行详细讲解,具体如下。
(1)MyBatis读取核心配置文件mybatis-config.xml:mybatis-config.xml核心配置文件主要配置了MyBatis的运行环境等信息。
(2)加载映射文件Mapper.xml:Mapper.xml文件即SQL映射文件,该文件配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。
(3)构造会话工厂:通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory,用于创建SqlSession。
(4)创建会话对象:由会话工厂SqlSessionFactory创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)创建执行器:会话对象本身不能直接操作数据库,MyBatis底层定义了一个Executor接口用于操作数据库,执行器会根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询。
(6)封装SQL信息:SqlSession内部通过执行器Executor操作数据库,执行器将待处理的SQL信息封装到MappedStatement对象中。
(7)操作数据库:根据动态生成的SQL操作数据库。
(8)输出结果映射:执行SQL语句之后,通过MappedStatement对象将输出结果映射至Java对象中。