框架 - mybatis(动力节点:老杜从零学mybatis入门到架构思维)(一)
一、MyBatis概述
-
框架
-
java常用框架:SSM三大框架、SpringBoot、SpringCloud等。
-
框架其实就是对通用代码的封装,提前写好了一堆接口和类,我们可以在做项目的时候直接引入这些接口和类(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率。
-
框架一般都以jar包的形式存在。(jar包中有class文件以及各种配置文件等。)
-
SSM三大框架的学习顺序:MyBatis、Spring、SpringMVC(仅仅是建议)
-
-
三层架构:
![]()
-
表现层(UI):直接跟前端打交互(一是接收前端ajax请求,二是返回json数据给前端)
-
业务逻辑层(BLL):一是处理表现层转发过来的前端请求(也就是具体业务),二是将从持久层获取的数据返回到表现层。
-
数据访问层(DAL):直接操作数据库完成CRUD,并将获得的数据返回到上一层(也就是业务逻辑层)。
-
-
JDBC的不足
- SQL语句写死在Java程序中,不灵活。改SQL的话就要改Java代码。违背开闭原则OCP。
- PrepareStatment给?传值是繁琐的。能不能自动化???
- 将结果集封装成Java对象是繁琐的。能不能自动化???
-
Mybatis是完全开源的,现在迁移到github上面了。可以到那儿去下载源码、jar包等:https://github.com/mybatis/mybatis-3
-
MyBatis简介
-
MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD。MyBatis在三层架构中负责持久层的,属于持久层框架。
-
MyBatis的发展历程:MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
-
iBatis一词来源于“internet”和‘batis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects (DAOs)。
-
打开mybatis代码可以看到它的包结构中包含:ibatis
-
-
ORM:对象关系映射
-
O (Object):Java虚拟机中的Java对象
-
R(Relational):关系型数据库
-
M (Mapping):将Java虚拟机中的Java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象。
-
示意图:
![]()
-
MyBatis框架就是一个ORM框架。MyBatis是一个半自动化的ORM,因为MyBatis框架中SQL语句是需要程序员自己编写的。
-
Hibernate框架就是一个全自动化的ORM。使用Hibernate框架的时候,不需要程序员手动编写SQL语句,SQL语句可以自动生成。所以Hibernate是一个完全的全自动化的ORM框架。
-
-
MyBatis框架特点:
- 支持定制化SQL、存储过程、基本映射以及高级映射
- 避免了几乎所有的JDBC代码中手动设置参数以及获取结果集
- 支持XML开发,也支持注解式开发。【为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。】
- 将接口和Java 的POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录
- 体积小好学:两个jar包,两个XML配置文件。
- 完全做到sql解耦合。
- 提供了基本映射标签。
- 提供了高级映射标签。
- 提供了XML标签,支持动态SQL的编写。
二、MyBatis入门程序
-
maven项目的resources目录:放在这个目录当中的,一般都是资源文件,配置文件。直接放到resources目录下的资源,等同于放到了类的根路径下。
-
具体步骤(参考官网)
-
打包方式jar(web项目才需要打包为war)
-
引入依赖:mybatis依赖、mysql驱动依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.14</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> -
编写mybatis核心配置文件:mybatis-config.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中可以配置多个数据源,也就是多个environment--> <environments default="development"> <!--一个environment代表了一个数据源--> <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/test"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> </environments> </configuration>
-
-
编写XxxxMapper.xml配置文件:(文件名和文件存放位置不是一定的)。注意这个文件中的一个select、insert、update、delete标签,就相当于在原生的JDBC中的Statement对象。
<?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="abcd"> <select id="insertCar"> insert into t_car values (null,'1003','丰田霸道',30.0,'2000-10-11','燃油车') </select> </mapper> -
需要将XxxxMapper.xml文件与mybatis-config.xml文件关联起来,只需要在mybatis-config.xml文件中加入以下配置:(resource属性自动会从类的根路径下开始查找资源)
<mappers> <!--resource属性自动会从类的根路径下开始查找资源。--> <mapper resource="mapper/TCarDao.xml"/> </mappers> -
在MyBatis当中,负责执行SQL语句的那个对象是SqlSession,SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。
-
要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。
SqlSessionFactory对象,就代表了一个environments下的environment,也就是代表了一个配置的数据源。
-
怎么获取SqlSessionFactory对象呢?需要首先获取SqlSessionFactoryBuilder对象。
-
通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。
-
mybatis的核心对象包括:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。(SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession)
-
-
编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
public static void main(String[] args) throws IOException { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // Resources.getResourceAsStream默认就是从类的根路径下开始查找资源。 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(in); SqlSession sqlSession = sessionFactory.openSession(); // 注意这个参数。 int insertCar = sqlSession.insert("insertCar"); System.out.println(insertCar); // mybatis默认不会自动提交,需要手动提交。 sqlSession.commit(); }
-
-
mybatis中有两个主要的配置文件:
-
mybatis-config.xml:这是核心配置文件,主要配置连接数据库的信息等。(一个)
-
xxxxMapper.xml:这个文件是专门用来编写SQL语句的配置文件。(一个表一个,也可以只使用一个配置文件,但是一般不这么干)
- t_user表,一般会对应一个UserMapper.xml
- t_student表,一般会对应一个StudentMapper.xml
-
-
关于第一个程序的小细节
-
mybatis中sql语句的结尾";"可以省略。
-
Resources.getResourceAsStream:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)采用这种方式来获取资源,代码可移植性比较强。
-
InputStream is = new FileInputStream("d:\mybatis-config.xml"):采用这种方式也可以。可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。
-
上面提到的,XxxxMapper.xml可以放到任意路径下,这个时候要加载该文件,就无法使用上述提到的方式,从类路径中加载了,所以就需要使用另一种方式来加载文件。
<mappers> <!--语法格式:file:///绝对路径--> <mapper url="file:///d:/CarMapper.xml"/> </mappers>
-
-
关于Mybatis的事务管理机制(可参考官网)
-
在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理
<!--environments中可以配置多个数据源,也就是多个environment--> <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/test"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> </environments> -
type属性的值包括两个:JDBC(jdbc)、MANAGED(managed),在mybatis中提供了两种事务管理机制,默认使用JDBC事务处理机制。
-
JDBC事务管理器:mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务;
例如SqlSession sqlSession = sessionFactory.openSession();这里会关闭事务自动提交,然后后面需要使用
sqlSession.commit();手动提交事务。
注意其实可以开启自动提交事务,也就是没有开启事务:SqlSession sqlSession = sessionFactory.openSession(true); -
MANAGED事务管理器:mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如spring。对于我们当前的单纯的只有mybatis的情况下,如果配置为MANAGED,那么事务这块是没人管的。没有人管理事务表示事务压根没有开启(也就是不用手动提交事务了)。
-
-
设置(settings):这是 MyBatis 中极为重要的调整设置,可以调整mybatis的一些参数配置,它们会改变 MyBatis 的运行时行为
<settings> <setting name="cacheEnabled" value="true"/> </settings> -
Mybatis集成日志logback
-
作用:让logback日志框架打印mybatis执行日志,方便之后的调试。
-
mybatis常见的集成的日志组件有哪些呢?SLF4J(沙拉风)、LOG4J、LOG4J2、STDOUT_LOGGING
-
其中STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。只要开启即可。怎么开启呢?在mybatis-config.xml文件中使用settings标签进行配置开启。
<settings> <setting name="logImpl" value="STDOUT_LOGGING "/> </settings> -
以下示例集成第三方的日志框架logback:
-
首先需要导入logback依赖
-
配置:
<settings> <setting name="logImpl" value="SLF4J"/> </settings> -
引入logback的xml配置文件
-
这个配置文件的名字必须叫做logback.xml或者logback-test.xml,不能是其它的名字。
-
这个配置文件必须放到类的根路径下,不能是其他位置。
-
-
配置文件示例:
<?xml version="1" 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> <!--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> -
这样就可以了,会自动打印mybatis的执行日志。
-
三、使用MyBatis完成CRUD
-
在上述提到的例子中,使用的sql,是直接写死在文件中的,这在真实项目中,肯定是不对的。一般而言,是类似JDBC代码,使用PrepareStatement,执行带有占位符?的代码,然后再插入需要的参数替换占位符,这才是正确的方式。那么在mybatis中,如何来实现?
-
使用正确的方式来实现insert操作(使用Map来传递参数)
-
首先是XxxxMapper.xml的编写,使用占位符:#{Map中存放的键值对的key值}
<mapper namespace="abcd"> <update id="insertCar"> insert into t_car values (null,#{key1},#{key2},#{key3},#{key4},#{key5}); </update> </mapper> -
使用Map来传递参数
public static void main(String[] args) throws Exception { // SessionUtils是一个工具类,封装了获取SqlSession的过程。 SqlSession sqlSession = SessionUtils.getSqlSession(); // 使用map保存参数 HashMap hashMap = new HashMap(); hashMap.put("key1","1003"); hashMap.put("key2","丰田霸道"); hashMap.put("key3",30.0); hashMap.put("key4","2000-10-11"); hashMap.put("key5","燃油车"); // 使用map传递参数,#{key}将获取到map中的值 int insertCar = sqlSession.insert("insertCar",hashMap); System.out.println(insertCar); sqlSession.commit(); sqlSession.close(); }
-
-
实现insert操作(使用实体类来传递参数)
-
定义一个实体类用来封装参数
@Data @NoArgsConstructor @AllArgsConstructor public class TCar { private Integer id; private String car_num; private String brand; private double guide_price; private String produce_time; private String car_type; } -
XxxxMapper.xml的编写,使用占位符:#{实体类属性名}
<update id="insertCar"> insert into t_car values (null,#{car_num},#{brand},#{guide_price},#{produce_time},#{car_type}); </update> -
使用实体类来传递参数
public static void main(String[] args) throws Exception { // SessionUtils是一个工具类,封装了获取SqlSession的过程。 SqlSession sqlSession = SessionUtils.getSqlSession(); // 使用自定义的实体类来保存参数 TCar tCar = new TCar(null,"1003","丰田霸道",30.0,"2000-10-11","燃油车"); // 使用map传递参数,#{属性名}将获取到实体类中的对应的属性值 int insertCar = sqlSession.insert("insertCar",tCar); System.out.println(insertCar); // 释放资源 sqlSession.commit(); sqlSession.close(); } -
如果在XxxxMapper.xml文件中,在#{属性名}中,将属性名修改为实体类中没有的,会报错:There is no getter for property named 'xyz' in 'class com.powernode.mybatis.pojo.Car',意思是找不到实体类中的getter方法,也就是说:#{属性名}获取属性值的操作,其实是去调用类的get方法。
严格意义上来说:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。
例如:getUsername() --> #{username}
例如:getEmail() --> #
-
-
delete操作
-
与插入操作是差不多的路数,没啥好说的。也就是最后换成使用SqlSession.delete()方法来执行sql就是了
-
注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。
-
-
update操作
-
与插入操作是差不多的路数,没啥好说的。也就是最后换成使用SqlSession.update()方法来执行sql就是了
-
同样可以选择使用pojo来封装参数,或者使用map集合。一般都会选择使用pojo的。
-
-
selete:查询一行数据
-
数据库中有一张表 t_car:
![]()
-
与表t_car对应的实体类:注意此处guidePrice、produceTime属性与表中的表项名不一致
@Data @NoArgsConstructor @AllArgsConstructor public class TCar { private Integer id; private String car_num; private String brand; private Double guidePrice; private String produceTime; private String car_type; } -
编写XxxMapper.xml:注意这里需要一个resultType的属性来指定查询之后的结果类型,因为SqlSession执行完毕之后,会返回ResultSet对象,然后将对象封装为某个数据类型,而如果没有指定要封装的类型,就会报错。
<mapper namespace="abcd"> <select id="seletById" resultType="shh.pojo.TCar"> select * from t_car where id = #{id} </select> </mapper> -
执行:
public class psvm { public static void main(String[] args) throws Exception { // SessionUtils是一个工具类,封装了获取SqlSession的过程。 SqlSession sqlSession = SessionUtils.getSqlSession(); Object car = sqlSession.selectOne("selectById", 1); System.out.println(car); sqlSession.close(); } } -
注意执行结果:TCar(id=1, car_num=100, brand=宝马520Li, guidePrice=null, produceTime=null, car_type=燃油车)。会发现guidePrice、produceTime属性的值没有成功查询出来,显示为null
分析:这是因为mybatis去数据库中查询表t_car,表中的属性为guide_price、priduce_time,与实体类中的对应不上,所以就封装失败了。
-
解决上述问题:可以采用取别名的方式,将表中的属性名取别名,与实体类中的属性名对应上即可。
<mapper namespace="abcd"> <!--必须使用resultType属性来指定查询结果的类型--> <select id="selectById" resultType="shh.pojo.TCar"> select id,car_num,brand,guide_price as guidePrice,produce_time as produceTime,car_type from t_car where id = #{id} </select> </mapper>
-
-
select:查询所有
-
执行时,需要使用sqlSession.selectList()方法,mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。
-
编写XxxMapper的时候,resultType还是指定要封装的结果集的类型。不是指定List类型,因为selectList方法默认就是返回一个List类型的,所以是指定List集合中元素的类型。
-
-
在XxxxMapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。
-
XxxxMapper.xml示例:
<mapper namespace="aaaa"> <!--必须使用resultType属性来指定查询结果的类型--> <select id="selectById" resultType="shh.pojo.TCar"> select id,car_num,brand,guide_price as guidePrice,produce_time as produceTime,car_type from t_car where id = #{id} </select> </mapper> -
假设还有另一个XxxMapper,里面的namspace属性是"bbbb",而且也有一个select标签,id为selectById。
-
这个时候如果以上面的sqlSession.selectOne("selectById", 1);来执行的话,会报错,因为selectById这个id重复了,mybatis不知道应该去执行哪一个。正确的执行方式是:sqlSession.selectOne("aaaa.selectById", 1);
-
四、MyBatis核心配置文件详解
-
configuration是根标签,所有的配置都需要在这个标签中进行,不能放到外面去。
-
environments标签
-
environments标签配置数据源,里面的一个environment标签代表一个数据源
-
下方是一个environments的示例,并配置了两个environment,id分别为代表了两个数据源test和smbms,配置默认使用test
<!-- environments中配置数据源,一个environment代表一个数据源--> <!--default属性是配置默认使用哪一个environment,使用environment的id值即可。--> <environments default="test"> <!-- environment代表一个数据源,也就是代表了一个数据库--> <environment id="test"> <transactionManager type="JDBC"/> <!--事务管理方式配置--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> <environment id="smbmd"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/smbms"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> </environment> -
一个environment 代表一个SqlSessionFactory,可以直接使用默认数据源,也可以指定数据源。(也就是指定一个environment,通过environment的id值)
// 获取一个使用默认数据源的SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); // 获取一个指定使用“test”数据源的SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"test");
-
-
environments标签:在environment中配置事务管理机制(可参考官网)
-
在mybatis-config.xml文件中,可以通过transactionManager标签配置进行mybatis的事务管理
<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/test"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> -
type属性的值包括两个:JDBC(jdbc)、MANAGED(managed),在mybatis中提供了两种事务管理机制,默认使用JDBC事务处理机制。
-
在mybatis中提供了一个事务管理器接口:Transaction。该接口下有两个实现类:
-
如果type="JDBC",那么底层会实例化JdbcTransaction对象。mybatic自己来管理事务
-
如果type="MANAGED",那么底层会实例化ManagedTransaction。让第三方容器来管理事务。
-
-
-
environments标签:datasource配置(可以参考官网)
-
数据源概述:
-
dataSource被称为数据源,数据源实际上是一套规范。JDK中有这套规范:javax.sql.DataSource(这个数据源的规范实际上是JDK规定的)
-
dataSource作用是什么?为程序提供Connection对象。(但凡是给程序提供Connection对象的,都叫做数据源。)
-
我们自己也可以编写数据源组件,只要实现javax.sql.DataSource接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。有了自己的数据源就可以在datasource标签中配置
-
常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?阿里巴巴的德鲁伊连接池 druid、c3p0、dbcp........
-
-
datasource标签的type属性,type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象。type属性有三个值(必须是三选一)。
-
UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。
-
POOLED:使用mybatis自己实现的数据库连接池。
使用连接池和不使用连接池的区别:不使用连接池的话,每次访问数据库都会新建一个连接,用完之后断开连接。使用连接池的话,会新建一些连接放到连接池中,当需要的时候就从连接池中获取连接,用完之后再放回池中即可,不需要经常进行新建、断开连接池的操作。
而建立连接和释放连接很影响性能,所以使用连接池性能较高,并且连接池中的连接数量有限且可控,不会造成建立过多连接导致数据库服务器宕机等危险。 -
JNDI:集成其它第三方的数据库连接池,如druid、c3p0等等。
-
-
使用POOLED类型的话,还需要配置一些连接池参数,参数配置好的话,可以提高一些效率,至于可以配置哪些参数,则可以从官网参考。至于参数要配置成什么,则需要依据显示情况来确定。
-
JNDI(java命名目录接口)是一套规范。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范。例如Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。
-
不同的数据源类型可以需要配置不同的属性。因为Tomcat实现了JNDI规范,所以可以直接使用Tomcat中集成的数据源,所以这种数据源配置只需要两个属性:initial_context 、data_source (参考官网吧)
-
-
properties标签
-
properties标签可以用于配置一些属性,这些属性可以在其它配置中获取,或者在程序中使用。
-
使用示例:
-
添加properties配置
<!--java.util.Properties类。是一个Map集合。key和value都是String类型--> <!--在properties标签中可以配置很多属性--> <properties> <!--这是其中的一个属性--> <!--<property name="属性名"value="属性值"/>--> <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbc.url" value="jdbc:mysql://localhost:3306/test"/> <property name="jdbc.username" value="root"/> <property name="jqbc.password" value=""/> </properties> -
配置的属性value可以在其他配置中使用${key}值来获取,如下:
<dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.url}"/> <property name="password" value=""/> </dataSource>
-
-
propertie的另一个用法(也是最实用的方法):
-
在resource目录下新建一个jdbc.properties文件:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root jdbc.password="" -
使用properties标签引用该文件,就可以达到直接扩展配置文件的效果:
<!--只要出现resource属性,那么就是从根路径下开始查找资源的。--> <properties resource="jdbc.properties"/> <!--从绝对路径当中加载资源。绝对路径怎么写?file:///路径--> <properties url="" /> -
使用方式与上面一样,就是使用${key}来获取配置的值即可。
-
-
五、手写MyBatis框架(掌握原理)
-
dom4j解析XML:
-
dom4j是java中用于解析xml的技术,需要引入以下依赖:
<!--dom4j的依赖--> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency> <!--jaxen依赖--> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.2.0</version> </dependency> -
用于解析的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> <properties resource="jdbc.properties"/> <environments default="test"> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> <environment id="smbms"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="jdbc:mysql://localhost:3306/smbms"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/TCarDao.xml"/> </mappers> </configuration> -
dom4j的基本使用示例:
@Test public void test01() throws Exception { // SAXReader对象用于读取xml文件 SAXReader reader = new SAXReader(); // 通过字节流读取mybatis-config.xml文档。封装到Document对象中。 Document document = reader.read(Resources.getResourceAsStream("mybatis-config.xml")); // 获取文档中的根标签 Element rootElement = document.getRootElement(); String name = rootElement.getName(); System.out.println(name); // 输出 configuration /* 通过路径匹配获取定位XML文件中的元素。 xpath是做标签路径匹配的。能够让我们快速定位XML文件中的元素。 以下的xpath代表了:从根下开始找configuration标签,然后找configuration标签下的子标签environments */ String path = "/configuration/environments"; Element environments = (Element)document.selectSingleNode(path); String defaultEnvironment = environments.attributeValue("default"); // 获取default属性值 System.out.println(defaultEnvironment); // 获取/configuration/environments/路径下的,id为的defaultEnvironment的environment path = "/configuration/environments/environment[@id='"+defaultEnvironment+"']"; Element environment = (Element) document.selectSingleNode(path); System.out.println(environment.attributeValue("id")); // 试用Element.element()方法获取获取子节点的对象 Element dataSource = environment.element("dataSource"); // 使用elements()方法获取所有子节点的集合 List<Element> properties = dataSource.elements(); for (Element property : properties) { System.out.println(property.attributeValue("value")); } // 获取所有的mapper标签 // 不想从根下开始获取,你想从任意位置开始,获取所有的某个标签,xpath该这样写 path = "//mapper"; List<Node> nodes = document.selectNodes(path); for (Node node : nodes) { System.out.println(((Element)node).attributeValue("resource")); } }
-
-
具体实现:略;
六、在WEB中应用MyBatis
-
场景示例:实现转账交易,使用MVC架构,使用mybatis框架。
![]()
-
编写servlet:
public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); resp.setCharacterEncoding("utf-8"); String fromActno = req.getParameter("fromActno"); String toActno = req.getParameter("toActno"); String money = req.getParameter("money"); AccountService accountService = new AccountServiceImpl(); String transferResult = accountService.transfer(fromActno, toActno, Double.parseDouble(money)); resp.getWriter().write(transferResult); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } -
编写service层:注意提交事务的操作
public class AccountServiceImpl implements AccountService { AccountDao accountDao = new AccountDaoImpl(); public String transfer(String from, String to, Double money) { Account from_account = accountDao.selectByNo(from); Account to_account = accountDao.selectByNo(to); if (from_account == null || to_account == null){ return "转账失败,账户不存在"; } if (from_account.getBalance() < money) { return "余额不足,转个jj的账啊"; } from_account.setBalance(from_account.getBalance()-money); to_account.setBalance(from_account.getBalance()+money); accountDao.updateBalanceByNo(from_account); accountDao.updateBalanceByNo(to_account); // 在这里提交事务,防止出现数据不一致 SqlSessionUtil.getSqlSession().commit(); return "ok"; } } -
编写一个创建SqlSession的工具类,使用了ThreadLocal,保证一个线程使用一个SqlSession,方便事务管理。
public class SqlSessionUtil { private static SqlSessionFactory factory; // 管理事务的时候,需要让一个线程只使用一个SqlSession,所以这个时候就可以使用ThreadLocal来实现了。 private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>(); static { try { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } // 为当前线程创建sqlSession或者获取当前线程创建的sqlSession public static SqlSession getSqlSession(){ SqlSession sqlSession = threadLocal.get(); if (sqlSession == null){ sqlSession = factory.openSession(); threadLocal.set(sqlSession); } return sqlSession; } // 关闭sqlSession public static void close(SqlSession sqlSession){ sqlSession.close(); threadLocal.remove(); } } -
当然还有mybatis-config.xml,XxxxMapper.xml文件要实现了,此处略过
-
编写Dao层接口,并编写实现类:
public class AccountDaoImpl implements AccountDao { public Account selectByNo(String no) { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); Account account = (Account) SqlSessionUtil.getSqlSession().selectOne("account.selectByNo",no); return account; } public int updateBalanceByNo(Account account) { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); int update = sqlSession.update("account.updateBalanceByNo",account); return update; } } -
注意mybatis三个核心类的生命周期
-
SqlSessionFactoryBuilder:这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory,就不再需要它了。因此SqlSessionFactoryBuilder实例的最佳作用域是方法作用域(也就是局部方法变量,不要一直保留着它,以保证所有的XML解析资源可以被释放给更重要的事情。
-
SqlSessidnFactory:SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。使用SqlSessionFactory的最佳实践是在应用运行期间不要重复创建多次,多次重建SqlSessionFactory被视为一种代码“坏习惯”。因此SqlSessionFactory的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
-
SqlSession:每个线程都应该有它自己的SqlSession实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。SqlSession 放在一个和HTTP请求相似的作用域中。换句话说,每次收到HTTP请求,就可以打开一个SqlSession,返回一个响应后,就关闭它。这个关闭操作很重要,为了确保每次都能执行关闭
-
-
分析Dao层实现类,发现接口的方法实现有一定的规律,就是获取SqlSession对象,然后执行XxxxMapper.xml中定义的sql,所以Dao层实现类理论上可以想办法自动生成,Mybatis中确实实现了自动生成Dao层的实现类,就是使用javassist组件。
-
到此处,总结mybatis的使用方式:
-
编写mybatis-config.xml核心配置文件
-
定义一个工具类获取SqlSession对象。
-
定义XxxxDao接口,定义方法实现某个数据库操作
-
定义XxxMapper.xml配置文件,编写sql
-
定义XxxxDao接口的实现类,在类中实现接口定义的方法,实现方式一般是:使用SqlSession对象的update、delete、selectOne、selectList、insert等方法,指定sqlId执行对应的sql操作。
public class AccountDaoImpl implements AccountDao { public Account selectByNo(String no) { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); Account account = (Account) sqlSession.selectOne("account.selectByNo",no); return account; } }
-
七、使用javassist生成类
-
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
与java反射机制的区别就是,java反射机制是去读取java字节码文件,而javassist可以实现创建、编辑、分析读取字节码文件。
-
Mybatis底层使用javassist来实现上面提到的Dao层的实现类。
-
首先需要引入javassist依赖:
<!--javassist依赖--> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.29.1-GA</version> </dependency> -
基本使用示例:
public static void main(String[] args) throws Exception{ // 获取类池,这个类池就是用来给我生成class的 ClassPool pool = ClassPool.getDefault(); // 制造类(需要告诉javassist,类名是啥) CtClass newClass = pool.makeClass( "shh.dao.impl.AccountDaoImpl01"); // 制造方法 String methodCode = "public void insert(){System.out.println(123);}"; CtMethod newMethod01= CtMethod.make(methodCode,newClass); // 将方法添加到类中 newClass .addMethod(newMethod01); // 在内存中生成class(其实同时会将类加载到JVM中) newClass .toClass(); // === 到这里,已经使用javassist完成类生成了。========== // 类加载到JVM当中,返回AccountDaoImpl类的字节码 Class<?> clazz = Class.forName("shh.dao.impl.AccountDaoImpl01"); // 创建对象 Object obj = clazz.newInstance(); // 获取AccountDaoImpl中的insert方法 Method insertMethod = clazz.getDeclaredMethod( "insert"); // 调用方法insert insertMethod.invoke(obj); } -
生成一个接口的实现类:
-
接口已经定义了:
public interface AInterface { void add(String name,int age); int delete(String no); int update(String no); String query(); } -
生成类代码:
public static void main(String[] args) throws Exception{ // 获取用于生成类的类池 ClassPool classPool = ClassPool.getDefault(); // 生成类 CtClass newClass = classPool.makeClass("shh.dao.impl.AInterfaceImpl"); // 生成接口 CtClass aInterface = classPool.makeInterface("shh.dao.AInterface"); // 生成的newClass继承aInterface接口 newClass.addInterface(aInterface); Class<AInterface> interfaceClass = AInterface.class; Method[] interfaceMethods = interfaceClass.getMethods(); // 开始编写实现类的方法 for (Method method : interfaceMethods) { // 拼接方法代码 StringBuilder methodCode = new StringBuilder(); methodCode.append("public "); methodCode.append(method.getReturnType().getSimpleName()+" "); methodCode.append(method.getName()+"("); Class<?>[] parameterTypes = method.getParameterTypes(); // 拼接参数列表 for (int i = 0; i < parameterTypes.length; i++) { methodCode.append(parameterTypes[i].getSimpleName()); methodCode.append(" arg"+i); if (i != parameterTypes.length -1){ methodCode.append(","); } } methodCode.append("){"); methodCode.append("System.out.println(\"我是方法\");"); // 处理返回值。 if (method.getReturnType().getSimpleName().equals("int")) { methodCode.append("return 11;"); }else if (method.getReturnType().getSimpleName().equals("String")){ methodCode.append("return \"我是你爸爸\";"); } // 完成方法代码的拼接。 methodCode.append("}"); // 生成方法,在类中加入方法 CtMethod newMethod = CtMethod.make(methodCode.toString(), newClass); newClass.addMethod(newMethod); } // 在内存和JVM中生成类。 Class<?> aInterfaceImpl = newClass.toClass(); // 创建类实例,利用多态执行类方法。 AInterface hh = (AInterface) aInterfaceImpl.newInstance(); hh.query(); hh.add("afds",12); hh.update("sf"); hh.delete("sfs"); }
-
-
使用javassist自定义生成XxxDao接口的实现类:(实际上也是mybatis的底层实现)
public static Object getMapper(Class daoInterface){ ClassPool classPool = ClassPool.getDefault(); CtClass daoImpl = classPool.makeClass(daoInterface.getName() + "Impl"); CtClass anInterface = classPool.makeInterface(daoInterface.getName()); daoImpl.addInterface(anInterface); // 实现类继承接口 Method[] methods = daoInterface.getMethods(); // 开始实现daoInterface的所有方法: for (Method method : methods) { // 拼接方法代码 StringBuilder methodCode = new StringBuilder(); methodCode.append("public "); methodCode.append(method.getReturnType().getName()+" "); methodCode.append(method.getName()+"("); Class<?>[] parameterTypes = method.getParameterTypes(); // 拼接参数列表 String params[] = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { methodCode.append(parameterTypes[i].getName()); params[i] = "arg"+i; methodCode.append(" arg"+i); if (i != parameterTypes.length -1){ methodCode.append(","); } } methodCode.append("){"); methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = shh.Utils.SqlSessionUtil.getSqlSession();"); Configuration configuration = getSqlSession().getConfiguration(); // 需要获取sql的id。mybatis中规定这个id是类名+方法名。 String sqlId = daoInterface.getName()+"."+method.getName(); // 需要知道是什么类型的sql语句 // sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。 // 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的。 // sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。 SqlCommandType sqlType = configuration.getMappedStatement(sqlId).getSqlCommandType(); String arg = ""; for (String param : params) { arg = arg+","+param; } switch (sqlType){ case DELETE: methodCode.append("return "); break; case INSERT: methodCode.append(""); break; case SELECT: methodCode.append("return ("+method.getReturnType().getName()+") sqlSession.selectOne(\""+sqlId+"\""+arg+");"); break; case UPDATE: methodCode.append("return sqlSession.update(\""+sqlId+"\""+arg+");"); break; } // 完成方法代码的拼接。 methodCode.append("}"); System.out.println(methodCode.toString()); // 生成方法,在类中加入方法 CtMethod newMethod = null; try { newMethod = CtMethod.make(methodCode.toString(), daoImpl); daoImpl.addMethod(newMethod); } catch (CannotCompileException e) { e.printStackTrace(); } } // 创建对象并返回。 Object o = null; try { Class<?> aClass = daoImpl.toClass(); o = aClass.newInstance(); } catch (Exception e) { e.printStackTrace(); } return o; } -
那么如果要使用mybatis实现的自动生成实现类的机制,就需要注意:sqlId都不能随便写。
-
namespace必须是dao接口的全限定名称。
-
id必须是对应的dao接口中方法名。
-
-
到此处,总结mybatis的使用方式:
-
编写mybatis-config.xml核心配置文件
-
定义一个工具类获取SqlSession对象。
-
定义XxxxDao接口,定义方法实现某个数据库操作
-
定义XxxMapper.xml配置文件,编写sql
-
使用SqlSession对象,自动生成XxxxDao的实现类即可(不再使用SqlSession对象的update、delete、selectOne、selectList、insert等方法)
-
不过需要注意:sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是对应的dao接口中方法名。
-





浙公网安备 33010602011771号