本笔记为本人在B站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。
框架概述
三层架构
界面层(视图层):和用户打交道的, 接收用户的请求参数, 显示处理结果的。(jsp ,html ,servlet)
业务逻辑层:接收了界面层传递的数据,计算逻辑,调用数据库,获取数据。
数据访问层(持久层层): 就是访问数据库, 执行对数据的查询,修改,删除等等的。
三层对应的包
界面层: controller包 (servlet)
业务逻辑层: service 包(XXXService类)
数据访问层: dao包(XXXDao类)
三层中类的交互
用户使用界面层–> 业务逻辑层–>数据访问层(持久层)–>数据库(mysql)
三层对应的处理框架
界面层—servlet—springmvc(框架)
业务逻辑层—service类—spring(框架)
数据访问层—dao类—mybatis(框架)
为什么要使用三层?
(1)结构清晰、耦合度低, 各层分工明确
(2)可维护性高,可扩展性高
(3)有利于标准化
(4)开发人员可以只关注整个结构中的其中某一层的功能实现
(5)有利于各层逻辑的复用
框架(Framework)
框架好比是一个规定好了某些内容的模板,需要根据一定的格式完成需要的内容。框架又好比是提供了许多基础的功能(灯光、背景、音乐)的舞台,加入自己的内容后才是完整的演出。
框架可以看作是一个半成品的软件,项开发者提供了许多可重复使用的基础功能,帮助完成某一领域的开发任务。
框架特点:
(1)框架一般不是全能的, 不能做所有事情。
(2)框架是针对某一个领域有效, 特长在某一个方面,比如mybatis做数据库操作强,但是他不能做其它的。
(3)框架是一个软件。
JDBC编程缺陷
(1)代码较多,开发效率低
(2)需要关注Connection、Statement、ResultSet对象的创建与销毁
(3)对ResultSet对象的查询结果需要进行人工封装
(4)重复代码多
(5)业务逻辑代码与数据库操作代码混合在一起
MyBatis框架概述
MyBatis是Apache的一个开源项目,前身是iBatis,是java语言的持久层框架,2013年代码迁移到GitHub上。
mybatis是 MyBatis SQL Mapper Framework for Java (sql映射框架)
(1)sql mapper :sql映射
可以把数据库表中的一行数据 映射为 一个java对象。
一行数据可以看做是一个java对象。操作这个对象,就相当于操作表中的数据。
(2)Data Access Objects (DAOs) :数据访问 , 对数据库执行CRUD。
回顾JDBC编程
1 | public void findStudent() { |
mybatis提供了以下功能:
(1)提供了创建Connection ,Statement, ResultSet的能力 ,不用开发人员创建这些对象了。
(2)提供了执行sql语句的能力, 不用你执行sql。
(3)提供了循环sql, 把sql的结果转为java对象, List集合的能力。
(4)提供了关闭资源的能力,不用你关闭Connection, Statement, ResultSet。
开发人员做的是: 提供sql语句。
总之,mybatis是一个sql映射框架,提供的数据库的操作能力。增强的JDBC,使用mybatis让开发人员集中精神写sql就可以了,不必关心Connection,Statement,ResultSet的创建,销毁,sql的执行。
MyBatis框架快速入门
Mybatis中文官方文档网址:https://mybatis.org/mybatis-3/zh/index.html
MyBatis入门例子
实现步骤
(1) 打开navicat,新建数据库ssm,新建一张student表
1 | create table student( |
(2) 在idea中新建空项目,命名为my_ssm_study
(3) 添加module,新建maven工程,选择maven-archetype-quickstart
骨架模板
(4) 填写工程坐标:
1 | GroupId:com.kwxy |
(5) Module name设置为:ch01-mybatis-first,点击finish,等待maven项目的构建,控制台显示BUILD SUCCESS字样,则构建成功
(6) 将src/main
和src/test
下的java
目录右键Mark Directory as-->Sources Root
(7) 在src/main
下新建resources
目录,右键Mark Directory as-->Resources Root
(8) 删除默认创建的APP类文件 ,这些文件分别在main和test目录中
(9) 删除pom.xml文件中无关的配置,将编译和运行jdk版本改为1.8
(10) 在build标签
中添加’指定资源位置’的配置,配置大致如下:
1 |
|
(11) 加入mybatis依赖和mysql驱动依赖
(加完最好右键pom.xml->Maven->Reimport
)
1 | <!--mybatis依赖--> |
(12) 创建Student类
:com.kwxy.domain.Student,生成对应的无参构造,有参构造,set/get,toString方法
1 | package com.kwxy.domain; |
(13) 定义持久层的dao
接口,定义操作数据库的方法
1 | //com.kwyx.dao.StudentDao 接口 |
(14) 创建一个Mybatis使用的xml配置文件,称之为sql映射文件或mapper文件。一般是一张表一个sql映射文件。此文件在接口所在的目录中,文件名与接口名保持一致
1 |
|
可以看到mapper文件中主要修改三个属性:namespace、id、resultType
(15) 创建mybatis使用的主配置文件。主配置文件提供了连接数据库的信息和SQL映射文件的位置。一般一个项目一个主配置文件
。
在src/main/resources
目录下创建mybatis-config.xml
文件,文件内容参照以下:
1 |
|
sql映射文件与Dao接口存在对应关系,放在了与Dao接口相同java目录下,因此需要在pom.xml中添加资源插件
:
1 | <resources> |
本来在执行compile命令
时,会默认将src/main/resources
下的内容复制到target/classes
中,但是在学习过程中发现,一旦添加了资源插件,resources目录下的主配置文件mybatis.xml并没有复制
到target/classes中。
而添加原有资源路径的插件后又成功了。因此建议
一旦添加资源插件,必须添加原有资源路径。
这个问题课程中的老师在后面提到了,并给出了一些不同于添加原有资源路径解决的办法:
1)清理后重新编译
2)build—->rebuild
3)file—->invalidate caches重启IDEA
4)直接复制mybatis.xml文件到classes目录。。。。(亏你想得出来)
这些方法2 3 我没尝试过,4我是不屑于尝试的。供诸位参考。
(16) 在src/test/java
下的包中创建测试类,创建测试方法通过mybatis向数据库插入一条数据
1 | public class TestMybatis { |
(17) 执行测试方法,查看控制台输出信息,并通过navicat查看数据库student表数据信息
1 | #idea控制台输出结果为: |
配置日志
在上述案例中,如果执行增删改操作,对开发人员来说执行的过程和结果不清晰:不知道执行了哪条sql语句,也不知道数据库发生了什么变化。
因此可以考虑配置日志信息。
在MyBatis的主配置文件mybatis.xml中,<configuration>
标签下加入如下配置:
1 | <!--settings:控制mybatis全局行为--> |
这样下次对数据库操作就会有具体操作信息输出在控制台。
MyBatis工具类的创建与使用
在之前提供的案例最终访问数据库的过程中,如果写不同方法访问数据库会有大量的代码冗余,主要是从第一步到第五步创建SqlSession对象为止,均是可重复的部分,因此可以创建一个工具类。
在创建工具类时注意重量级对象SqlSessionFactory只创建一次,所以放到静态代码块中。
1 | public class MyBatisUtils { |
现在创建测试方法使用工具类通过mybatis查询数据库中学生数据。
1 |
|
执行测试方法,查看控制台结果
1 | Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl'adapter. |
MyBatis主要对象介绍
Resources类
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。
1 | InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); |
SqlSessionFactoryBuilder 类
SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。
由于 SqlSessionFactoryBuilder 对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。
所以,一般会将该 SqlSessionFactoryBuilder 对象创建为一个方法内的局部对象,方法结束,对象销毁。
1 | SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); |
SqlSessionFactory 接口
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用 只需要一个该对象即可。
接口实现类: DefaultSqlSessionFactory
SqlSessionFactory作用: 获取SqlSession对象。
1 | SqlSession sqlSession = factory.openSession(); |
创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。
➢ openSession(true):创建一个有自动提交功能的 SqlSession
➢ openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
➢ openSession():同 openSession(false)
SqlSession接口
定义了操作数据的方法 例如 selectOne() ,selectList() ,insert(),update(), delete(), commit(), rollback() 。
SqlSession接口的实现类DefaultSqlSession。
使用要求: SqlSession对象不是线程安全的,需要在方法内部使用, 在执行sql语句之前,使用openSession()获取SqlSession对象。
在执行完sql语句后,需要关闭它,执行SqlSession.close(). 这样能保证他的使用是线程安全的。
MyBatis框架Dao代理(重点)
传统dao执行方式
创建DAO接口的实现类
,重写其操作数据库的方法:
(1) 根据mybatis的工具类,获得sqlsession。
(2) 指定执行Sql语句的标识。 sql映射文件中的namespace+”.”+标签的id值。
(3) 通过sqlId找到sql语句并执行。
(4) 关闭SQL session。
(5) 将结果返回。
以后访问操作数据库时,调用DAO接口的相应方法即可。
1 | public class StudentDaoImpl implements StudentDao { |
1 |
|
存在问题:
Dao 的实现类其实并没有干什么实质性的工 作,它仅仅就是通过 SqlSession 的相关 API 定位到映射文件 mapper
中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以,MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对 DB 进行操作。这种对 Dao 的实现方式称为 Mapper 的动态代理方式。
Mapper 动态代理方式无需程序员实现 Dao 接口
。接口是由 MyBatis 结合映射文件自动生成的动态代理
实现的。
动态代理的应用
动态代理的使用条件分析
当我们创建好DAO接口,并定义好操作数据库的抽象方法时,能否自动创建Dao接口的实现类呢:
1)传统dao执行方式第1步
获得sqlsession的步骤是固定的,可以通过工具类获得。
2)可以根据动态代理,获得此接口的全限定名,即sql映射文件namespace的值;也可以获得方法名,即sql映射文件对应语句的id值,也就得到了传统dao执行方式中的第2步的sql标识
。
3)根据方法的返回类型和sql标识指定的sql标签(select,insert,delete或update)可以确定sqlSession调用的方法(delete?还是selectList?或者是insert?update?)因此传统dao执行方式中的第3步
也是可以实现的。
4)传统dao执行方式第4步和第5步
是容易实现的。
因此,可以通过动态代理的技术自动创建Dao接口的实现类。
MyBatis的Dao动态代理代码实现
myBatis根据Dao方法的调用,获取执行Sql语句的信息。MyBatis根据Dao接口自动创建实现类,并创建对象。完成sqlSession调用方法,访问数据库。(不需要手动创建Dao接口的实现类)(在Dao接口中尽量不要使用方法重载
)
1 | public class TestProxy { |
深入理解参数
参数类型(parameterType)
是mapper文件中sql标签的一个属性,属性值是java数据类型的全限定名或mybatis定义的别名,表示DAO接口中方法的参数类型,例如当我们在Dao接口中定义了根据id查询数据的方法:
1 | Student selectStudentById(int id); |
在mapper文件中:
1 | <select id="selectStudents" parameterType="java.lang.Integer" resultType="com.kwxy.domain.Student"> |
在实际开发过程中,由于在myBatis中的参数类型可以通过反射机制
获得,parameterType属性因此可以不写
。
一个简单类型参数的传递
简单类型: MyBatis把java的基本数据类型和String都叫简单类型。
在mapper文件获取简单类型的一个参数的值,使用 #{任意字符}
多个参数的传递方式
第一种方式:使用@Param命名参数
当 Dao 接口方法多个参数,需要通过名称使用参数。在方法形参前面加入@Param(“自定义参数名”)。
1 | List<Student> selectStudentsByNameOrAge(@Param("myname") String name, @Param("myage") Integet age); |
mapper 文件使用#{自定义参数名}。
1 | <select id="selectStudentsByNameOrAge" resultType="com.kwxy.domain.Student"> |
第二种方式:对象传参
创建一个java对象,这个对象包含多个属性,这样在传参时传入这个Java对象,就将多个属性传入,达到了传递多个参数的目的。注意创建的java对象仍然需要封装。
在获取参数值时,完整的语法格式:#{属性名,javaType="java类型",jdbcType=“jdbc类型”}
例如:#{name,javaType="java.lang.String",jdbcType=“VARCHAR”}
实际上经常使用只写属性名的简化格式#{name}
,javaType与jdbcType可以通过反射机制获取。
第三种方式按位置传参(了解)
根据抽象方法定义时参数的前后位置(从0开始),在mapper文件中获取参数值,格式:#{arg位置下标}
。
例如:#{arg0}
表示第一个参数的值。
MyBatis3.4
及之前的版本没有关键字arg
。
这种方式不常使用。
第四种方式:使用Map(了解)
创建一个Map<String, Object> params = new HashMap();
向其存入参数。
例如:params.put("name","zhangsan"); params.put("age",24);
定义抽象方法时将这样一个Map传入。
在Mapper文件中,使用的语法#{Map的key}
。例如#{name}
得到了”zhangsan“这样一个值。
这种方式不常使用。
# 与 $
select id,name, email,age from student where id=#{studentId}
‘#’ 的结果: select id,name, email,age from student where id=?
使用占位符,使用PreparedStatement
对象
‘$’ 的结果:select id,name, email,age from student where id=1001
相当于
String sql=”select id,name, email,age from student where id=” + “1001”;
使用的Statement
对象执行sql, 效率比PreparedStatement低。
$ 可以替换表名或者列名, 你能确定数据是安全的。可以使用 $
1 | # 和 $ 区别 |
封装MyBatis输出结果
resultType
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
处理方式:
1.执行完sql语句,调用resultType的值代表类的无参构造器;
2.将每一条数据的所有字段按字段名调用同名属性的setter,然后放入List。
数据转化成java对象的任意的,不一定是实体类。
1)简单类型
例如分组函数的返回类型为简单类型。
count(*) 的resultType可以是”java.lang.Integer”或者别名”int”.
2)对象类型
对于自定义的java对象,用使用其全限定名称作为resultType,也可以自定义别名。
在MyBatis的主配置文件<settings>
标签之后添加标签:
1 | <typeAliases> |
应尽量避免使用别名,而是使用全限定名称,避免别名相同产生不必要的麻烦。
3)Map
sql 的查询结果作为 Map 的 key 和 value。推荐使用 Map<Object,Object>。
注意:Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录是错误。
resultType = "java.util.HashMap"
resultMap
结果映射。指定列名与java属性的对应关系。
1)自定义列值赋给哪个属性。
2)当属性名与列名不同时,必须通过这种方式指定映射关系。
在mapper文件中的mapper标签下:
1 | <!--首先定义出一个Map映射关系--> |
rusultType与resultMap不能同时存在
当使用多表联查的时候,要使用resultMap
通过 属性成员
将2个类建立起联系
一对一映射使用:association
例如:
1 | <!-- Person类的成员属性是Department类的实例 --> |
一对多映射使用:collection
实体类属性名与列名不同的处理方式
方法一:如上所述,使用resultMap
方法二:在SQL语句中,使用关键字as
使用列别名的方式, resultType的默认原则是同名的列值
赋值给同名的属性
, 使用列别名(java对象的属性名)
as可以省略,但是不建议,增加可读性
模糊查询
回顾sql中模糊查询:
关键字:like
通配符:(1)% 表示任意多个字符 (2)_ 表示任意一个字符
MyBatis实现模糊查询:
方法1:java代码指定like内容
例如指定查询姓名中带”李”的:
java文件中:
1 | String name = "%李%"; |
而在mapper文件中通过占位符#{…}获取查询的内容:
1 | <select id="selectByVagueName" resultType="com.kwxy.domain.Student"> |
sql语句预编译成了 like '%李%'
方法2:在mapper文件中拼接like的内容
java文件中:
1 | String name = "李"; |
而在mapper文件中通过占位符#{…}获取查询的内容:
1 | <select id="selectByVagueName" resultType="com.kwxy.domain.Student"> |
sql语句预编译成了 like "%"'李'"%"
MyBatis框架之动态SQL(重点)
动态sql: sql的内容是变化的,可以根据条件获取到不同的sql语句。主要是where部分发生变化。
动态SQL之if
使用案例:
Dao中定义了一个查询方法,参数是一个实体类型对象:
1 | List<Student> selectIf(Student stu); |
现要求根据此对象属性的值的内容不同来修改查询条件:
1)当name属性不为空时,添加查询条件name字段与name属性值相同;
2)当age属性值大于0时,添加查询条件age字段的值大于age属性值。
解决方法,在mapper文件中:
1 | <select id="selectIf" resultType="com.kwxy.domain.Student"> |
where 1=1
是为了防止第一个条件不成立,而后面的条件成立造成sql语法错误。因此添加一个永真条件,并在原来的第一个查询(本例中的name=#{name})条件前加and,因为像这种if
是无法去掉多余的 and ,or
的。
动态SQL之where
用来代替sql语句中的where关键字,包含if标签。当有一个if标签的test条件成立,就会自动给sql语句中添加where关键字。
当第一个if条件判断不成立时,会自动去掉第一个成立的if标签sql片段的连接词(and、or)。
例如之前案例中的select标签可以写成:
1 | <select id="selectWhere" resultType="com.kwxy.domain.Student"> |
即使第一个if不成立,第二个if成立,MyBatis也会自动生成语法正确的sql语句并执行。
就不需要再写一个类似 ”1=1“ 永真的条件加在sql主句中了。
动态SQL之foreach
foreach主要是为了完成sql语句中in关键字包含的数目不定的字段的值。
例如查询id是1001、1002、1003的学生信息,使用SQL语句:select * from student where id in (1001, 1002, 1003);
类似这样的字段值,我们一般保存在List或数组中,然后将List或数组作为Dao接口中抽象方法的参数。
foreach标签是帮助MyBatis遍历这个List或数组,并将其中的元素按sql的语法格式组合在sql语句中。
例如上述例子,我们在mapper文件中:
1 | <select id="selectForeach" resultType="com.kwxy.domain.Student"> |
collection属性的值只能是list
或array
。
当集和中保存到是实体对象时,通过#{item.属性名}
的方式获得其值。
其”.属性名“等同于调用此属性的getter方法。
其中:
1 | #collection: 表示接口中的方法参数的类型, 如果是数组使用array , 如果是list集合使用list |
动态SQL之代码片段
这是sql语句复用的一种方式。
(1) 在mapper标签下:
1 | <sql id="studentSql">select * from student</sql> <!--id是自定义的标识。--> |
(2) 在要使用sql标签中包含的sql片段的地方使用如下代码:
1 | <include refid="studentSql"/> <!--其中refid是要使用的sql标签的id属性值。--> |
MyBatis框架配置文件
主配置文件
settings:
配置myBatis全局行为。参加官方参考文档3.1.2(了解即可,大部分设置使用默认值)
dataSource标签
见MyBatis入门例子中的第 (15) 步MyBatis主配置文件。
事务
见MyBatis入门例子中的第 (15) 步MyBatis主配置文件。
使用数据库属性配置文件
将数据库的连接信息放在一个单独的文件中进行管理。和myBatis主配置文件分开,便于修改、保存,或者处理多个数据库信息。
配置文件的文件名:XXX.properties
配置文件的文件内容:等同于JDBC中提到的配置文件的内容。
采用key=value的形式,key使用”.”进行多级分隔。例如,在resources目录创建jdbc.properties文件:
1 | com.mysql.jdbc.Driver = |
在主配置文件中,首先在configuration标签下指定jdbc.properties文件的位置信息:
1 | <properties resource="配置文件从类路径(target/classes)开始的相对目录路径" /> |
然后在property标签中使用到value的位置用${key}来代替:
1 | <!--数据库的驱动类名--> |
typeAliases(类型别名)
mappers(映射器)
当有多个mapper文件时:
方法一:写多个mapper标签,如下:
1 | <mapper resource="com/kwxy/dao/XXXDao.xml"/> |
方法二:使用包名。
1 | <package name="com.kwxy.dao" /> |
使用package的要求:
(1) mapper文件名称需要和接口名称一样, 区分大小写的一样
(2) mapper文件和dao接口需要在同一目录
MyBatis中mapper配置文件名称和Dao接口名称是否要一致及原因
(1) 当核心配置文件mapper标签下以resource形式指向依赖配置文件是,不需要一致,
这样就可以加载到其相应的依赖配置文件通过namespace找到其相应的办法。
(2) 如果mapper标签下以package包扫描形式时,需要。
原因如下:
(1) 包扫描形式时,实体类+Mapper接口通过动态代理调用方法
(2) 调用方法时会找其相应的映射配置文件
(3) 当多个mapper接口和mapper.xml同时存在,如果没有相同的名称,则动态代理就不能通过
其一一对应的依赖配置文件创建其相应的实现方法
总结:MyBatis中mapper配置文件名称和Dao接口名称最好一致。
扩展
PageHelper
完成数据分页功能。
(1) 在Maven中添加PageHelper依赖
1 | <!-- mybatis分页插件依赖 --> |
(2) 在MyBatis主配置文件中加入plugin插件配置
1 | <!--在<environments>之前加入--> |
(3) 使用
查询语句之前调用 PageHelper.startPage 静态方法。
除了 PageHelper.startPage 方法外,还提供了类似用法的 PageHelper.offsetPage 方法。
在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可
紧跟在这个方法后的第一个 MyBatis 查询方法
会被进行分页
在获取到dao接口对象之后,执行抽象方法之前,调用静态方法PageHelper.StartPage,调用抽象方法之后就自动实现了分页查询。
1 | PageHelper.StartPage(int pageNum, int pageSize);//pageNum表示页数,从1开始,pageSize表示每页行数。 |
1 |
|
发布时间: 2020-07-21
最后更新: 2023-01-27
本文标题: MyBatis学习笔记
本文链接: https://blog-yilia.xiaojingge.com/posts/5177b49d.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!
