Mybatis使用及原理
Mybatis
mycat作用
读写分离
分库分表
工作使用并不会有很大的感受,仅仅是在配置文件中进行配置,然后让其生效。因为只是连接的mycat创建的一个虚拟的客户端什么是mybatis
是基于ORM的持久性框架,封装JDBC。
ORM 面向对象的封装方式
mybatis主要采用的技术是映射,通过映射的方式传递信息由框架进行解析,然后与数据库进行交互最后完成对数据集的持久化操作。预编译
可以优化sql,可以避免在程序编译运行的时候再次进行加载。 解决sql注入和提高查询效率 # $ 的区别
sql注入问题 $引起
# 是会在占位符替换后加上‘ ’ $ 是直接替换 不会添加''mybatis的映射方式
1.resultMap/type
2.查询后的结果定义别名使用时,必要的接口对应条件九种动态sqlhibernate和mybatis的区别(重点)
自动和半自动的区别hibernate由于全自动,所以不是开箱即用。故使用相对复杂 hql语句 通用所有查询语法 完全实现jba接口mybatis是半自动 更加灵活 可以自定义sql语句 sql语法 没有完全实现jba接口
Mybatis的介绍
mybatis是Apache软件基金会下的一个开源项目,前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下,改名为mybatis。2013年11月又迁移到了github。是持久层框架,用来访问数据库,只是众多持久层框架中一个,也是ORM框架:对象关系映射框架。
优缺点❤️
mybatis的优点1. 简单易学:mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件即可。
2. 使用灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。SQL语句写在XML里,便于统一管理和优化。
3. 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易进行单元测试。SQL语句和代码的分离,提高了可维护性。mybatis的不足1. 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
2. SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
3. 框架还是比较简陋,功能尚有缺失,二级缓存机制不佳
ORM映射机制(Object Relational Mapping 对应关系映射)
什么是ORM?将关系型数据库映射成面向对象编程的语言,如果使用框架,这由框架去完成。mybatis主要采用的技术是映射,通过映射的方式传递信息由框架进行解析,然后与数据库进行交互最后完成对数据集的持久化操作。
Mybatis的两种映射方式
java与数据库表所对应的关系: 类 -------> 表结构 对象------>记录 类成员---->字段名
1.XML配置文件
核心配置文件
核心文件本质上在mybatis的底层会创建出一个configuration的实体类,该配置文件有很多标签,各个标签的位置不可变,必须从上到下排列,可以省略。下列仅列举常用标签顺序。在xml文件中点击某一个标签可以进入到源码中查看顺序
properties
设置外部属性文件(properties),以替代其他位置中的值,${文件中的key}
properties标签中也有url属性,连接其他地址的配置文件
如果是用第二种写法,那么在对应的调用出要使用${property标签中的name}
有两种写法,如下
<!--引入数据库连接的配置文件-->
//第一种写法
<properties resource="jdbc.properties"/>
//第二种写法
<properties><!--在文档内定义键和值,文件要放在src否则找不到路径--><property name="jdbc.driver" value="com.mysql.jdbc.Driver"/><property name="jdbc.url" value="jdbc:mysql://localhost:3306/day25"/><property name="jdbc.username" value="root"/><property name="jdbc.password" value="root"/>
</properties>
注意: 当两种方式都使用的时候,以resource的文件内容为主执行流程是先执行内容的property标签再执行resource,所以会进行覆盖。
settings
设置其他文件信息 如log4J的配置信息
<!--配置LOG4J-->
<settings><setting name="logImpl" value="log4j"/>
</settings>
typeAliases
为实体类设置别名,如果不配置该属性则所有类均必须使用全限定类名作为查找依据
有两个属性标签,package和typeAliase 一个是给整个包下的所有类设置别名,一个是为单个类设置别名,可以存在多个子标签
package标签中的属性是name
typeAlias标签中的属性是type
<!--起别名-->
<typeAliases><package name="com.itheima.bean"/><typeAlias type="com.itheima.bean.Classes"/><typeAlias type="com.itheima.bean.Course"/>
</typeAliases>
在mybatis中已经定义很多别名,我们可以直接使用
内置别名
别名是小写的,以下类型可以直接使用
parameterType="java.lang.Integer" 换成 parameterType="int"
别名 | 真实类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
说明:
内置别名可以直接使用
别名不区分大小写
自定义类的别名就是类名
plugins
额外插件时需要进行配置的标签,如使用插件标签PageHelper的时候
<!--集成分页助手插件-->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
environments
对JDBC连接信息进行设置,指定连接池和事务管理器
transactionManage标签 事务管理器
type可以指定为
- JDBC(使用JDBC的事务)
- Managed(使用Spring,web容器)
dataSource标签 数据源信息 (连接池)
type可以指定为
Pooled 使用
Unpooled 不使用
JNDI 用名字获取web中资源
3.可以有多个environment标签来指定多个数据库信息
但是最终使用的数据库以environments标签中default=“id名”的哪一个为准
<!--environments配置数据库环境,环境可以有多个。default属性指定使用的是哪个-->
<environments default="mysql"><!--environment配置数据库环境 id属性唯一标识--><environment id="mysql"><!-- transactionManager事务管理。 type属性,采用JDBC默认的事务--><transactionManager type="JDBC"></transactionManager><!-- dataSource数据源信息--><dataSource type="POOLED"><!-- property获取数据库连接的配置信息 --><property name="driver" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment>
</environments>
mappers
有两种方式进行加载映射配置文件,在此标签下可以对多个包或者文件进行加载
方式一:加载单个映射文件mapper
方式二:包扫描加载映射文件package
<!-- mappers引入映射配置文件 --><mappers><!--子元素:mapper属性:resource:加载类路径下指定的配置文件,注:分隔符是/,而不是点号url: 读取指定路径下配置文件,或者网络的配置文件<mapper url="file:///d:/UserMapper.xml"/>class: 指定接口的完全限定名,用于注解的配置,不需要XML文件。<mapper class="com.itheima.dao.UserMapper"/>子元素:package1. 指定扫描哪个包下所有的DAO接口,如果使用这种写法,接口名与配置文件名字要相同。如:接口名UserMapper.java 配置文件名:UserMapper.xml2. 接口与配置文件必须放在同一个包下--><!-- mapper 引入指定的映射配置文件 resource属性指定映射配置文件的名称(单个) --><mapper resource="StudentMapper.xml"/> //放在src下时,所指定的xml配置文件<mapper class="com.itheima.dao.UserMapper"/> //指定接口,用.隔开。注解时直接用;配置文件时,配置文件要在该接口文件下 <package name="com.itheima.dao"/> //包名的全限定名,采用配置文件时,配置文件名字与接口名要相同</mappers>
2.映射配置文件
明确:
全限定名
根标签mapper的设置 name命名空间必须为对应的接口的
动态代理技术
mybatis的底层是使用的动态代理技术,所以才可以通过我们指定的接口,找到对应的方法进行执行再返回数据给调用处。在使用mybatis框架的时候,主要是对接口和映射配置文件进行操作。
事务的提交
在mybatis中,默认是手动提交的,对增删改进行操作的时候,如果不开程序没有错误的时候就不会报任何错误。也不会出现任何提示。数据库不会有任何更改。说明:如果在同一个方法中,有多个DML数据库操作,需要使用手动提交的方式。在mybatis的核心类工厂中提供了重量级
自动提交:
SqlSession sqlSession = sqlSessionFactory.openSession(true);
手动提交需要自己进行提交或者回滚操作,运用场景与转账一致:
sqlSession.commit();
sqlSession.rollback();
占位符
写SQL语句的时候,由于JDBC使用的是preparedStatement,所以在指定参数的时候必定有占位符,mybatis提供的占位符为
#{名字随意但要有意义},但是如果传递进来的参数为实体类时,那么#{}中必须填实体类对象的成员变量名
当sql进行解析后自带‘ ’ ,不需要再写‘ ’,如:
UPDATE student SET name = #{name},age = #{age} WHERE id = #{id} ;
在sql中的写法:UPDATE student SET name = ‘ 张三 ’,age = ‘ 23 ’ WHERE id = 2;
还提供${value}进行解析后不带‘ ’ ,此方法有SQL注入问题且变量名必为value。
映射文件的三种输入类型
主要针对parameType的填写1. 简单类 八大基本类型具体书写格式可参照typeAliases的别名规范2. POJO类(javaBean)- 实体类自己写的单一类 例如User,Student等,主要用于单表查询当传入的是实体类的时候,必使用实体类的名字而不是随意乱写- 实体类中含实体类自己写的类grade中含有多个其他类作为成员变量存在 如:user ,string ,int此时若传入类型resultType=grade ,那么在sql语句中应该用user.user的成员变量来进行调用如:select * from student where name like '%' #{user.name} ''%'
映射文件的两种输出类型
主要针对resultType的填写1. 简单类 八大基本类型
2. 实体类 在接口处使用集合来进行封装的时候 ,配置文件中填写泛型的类型,底层自动封装为集合返回
事务特性❤️
1.原子性 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。
2.一致性 事务开始前和结束后,数据库的完整性约束没有被破坏 。
3.持久性 事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
4.隔离性 同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰
小结
接口与xml映射配置文件要放在同一包下,接口名与配置文件名要一致
满足这个条件后,mappers中才可以class,resource属性,package标签通用,使用注解方式优先使用package标签扫描整个包
配置文件的命名空间要使用接口的全限定类名
增删改时要提交事务 opensession(true)。
传入的参数为POJO的第二种形态时要使用对象。成员变量为sql的占位符进行赋值
接口的参数和配置文件中定义规则
接口中的名字=配置标签中的id
接口中的形参=parameterType
接口中的返回值=resultType/resultMap
接口中的方法名不可有重载方法
核心配置文件的顺序必须由上到下依次排列(不用的省略掉)
Mybatis开发
增删改
增删改的标签是可以进行通用的,为何可以通用而不会报错?因为底层逻辑是增删改都使用了同一套执行流程,对传进来的语句和占位符进行赋值,然后直接返回。(具体在JDBC文档中查看对mybatis底层的框架模板)
增删改查的主要标签的各个含义
- id 该标签的名字,通过该id名查找或者调用
- resultType/resultMap 执行语句之后返回的结果集
- parameterType 调用此方法时前方传来的数据类型 可以省略
增
提供子标签 selectKey
作用是在插入一条语句的同时,可以获取一个该对象插入之后的主键值。
注解
/*** 添加用户* @SelectKey 获取新添加的记录主键值* 属性:* keyColumn:表中主键列名* keyProperty:实体类中主键的属性名* resultType: 主键返回的数据类型* before: true表示在insert语句前执行,false表示在insert语句后执行* statement: 要执行的SQL语句*/ @Insert("insert into user values(null, #{username},#{birthday},#{sex},#{address})") @SelectKey(statement = "select last_insert_id()" ,keyColumn = "id", keyProperty = "id", resultType = Integer.class, before = false) int insertUser(User user);
xml
两种方式
子标签selectKey
2. 在insert标签中 调用属性useGeneratedKeys=“true” (oracle不支持)其他标签不支持该功能,因为其他标签是可以通过查询来得到对应的主键值的。
注意:
在插入语句之后就要马上执行该子标签
子标签的各个参数含义
selectKey :获取新加的主键值
keyColumn: 表中主键列名
keyProperty:实体类中主键属性名
resultType: 主键的数据类型,int类型
order:在insert执行前,还是执行后执行这个查询
标签体:要执行的查询语句3.通过对象的getId()得到新增的主键值
<insert id="insert" parameterType="student">INSERT INTO student VALUES (null,#{name},#{age})<selectKey keyColumn="id" resultType="int" keyProperty="id" order="AFTER" >select last_insert_id(); //这个方法是数据集的聚合函数方法 </selectKey> </insert>
<!-- parameterType可以省略 如果参数是实体类,花括号中是属性名:#{属性名} keyProperty:实体类中主键属性名 keyColumn:表中主键列名 useGeneratedKeys: 使用生成的主键 使用这种方式的前提是:数据库本身有主键自动增长的功能,如:mysql, sql server。注:Oracle没有 --> <insert id="addUser" parameterType="user" keyProperty="id" keyColumn="id" useGeneratedKeys="true">insert into user values (null,#{username},#{birthday},#{sex},#{address}) </insert>
System.out.println(student.getId());
删
改
查
select
使用配置文件查询
注:
1.自动封装类名与字段名
resultType是默认自动封装,如果名字不符无法封装。
在resultMap中,设置标签autoMapping=true 自动封装,如果名字不符无法封装。
当类名是驼峰命名,表名是标准命名,可以开启核心配置文件中的mapUnderscoreToCamelCase
如: 类中变量 userName 字段名 user_name (sql中的命名规范)就可以在不指定名字的情况下进行自动映射
<settings><!--映射下划线为驼峰命名法,会自动将create_time对应createTime--><setting name="mapUnderscoreToCamelCase" value="true"/><!--log4j--><setting name="logImpl" value="log4j"/><!--开启延迟加载--><setting name="lazyLoadingEnabled" value="true"/></settings>
2.使用resultMap的时候,必定有一个类中存在需要查询的所有字段或者间接拥有(实例对象/集合<实例对象>),否则无法达到理想查 询目标,如果查询类中存在对应字段,但是没有使用resultMap映射,则输出时对应成员变量为null。
3.resultMap进行封装的时候,实际上是通过对应的表查询出来的id来封装的
单表查询
单表查询使用 resultType
多表查询
多表查询使用resultMap
因为该属性的作用是按照指定类(type属性)中各个变量名对应进行封装,多表查询列名不确定,故需要指定。
多表查询的一对一,一对多,多对多都需要指定resultMap进行封装
此处有两种方式,第一种更为简便。
1.使用一个resultMap进行整合,主类中有多个成员变量,则有多个collection或association标签
若autoMapping=“true”,相同字段名同样需要一一映射(不建议开,影响可读性)
使用到的关键属性名 ofType 指定property确定的成员变量的数据类型
//核心配置文件
<mappers><mapper resource="com/itheima/one_to_many/OneToMany.xml"/>
</mappers>
//接口
public interface OneToManyMapper {//查询全部public abstract List<Classes> selectAll();
}
<mapper namespace="com.itheima.table01.OneToOneMapper"><!--id:表示接口中方法名resultType:表示方法返回的数据类型,如果返回的是集合,这里指定集合中每个元素类型select标签标签体就是SQL语句--><resultMap id="selectaaaaaa" type="Card" autoMapping="true"><id property="id" column="cid"/><!--开了autoMapping="true"就会自动映射相同名字的--><!--<result property="number" column="number"/>-->//下面是card类中的 实例对象 Person p<association property="p" javaType="Person" autoMapping="true"><id property="id" column="pid"/><!--<result property="name" column="name"/>--><!--<result property="age" column="age"/>--></association>//下面是card类中的 集合对象 list<course> courses<collection property="courses" ofType="Course"><id property="id" column="cid"/><result property="name" column="cname" /></collection></resultMap><select id="selectAll" resultMap="selectaaaaaa" >SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id</select>
</mapper>
- 使用配置文件自关联
指定DeptInfo的对应映射关系
<resultMap id="BaseResultMap" type="DeptInfo"><id column="dept_id" jdbcType="VARCHAR" property="deptId"/><result column="dept_name" jdbcType="VARCHAR" property="deptName"/><result column="parent_id" jdbcType="VARCHAR" property="parentId"/><result column="state" jdbcType="DECIMAL" property="state"/><!--自关联属性--> parent是一个实体类DeptInfo Column是指定自关联的字段名调用对应的查询方法,就会作为传递值传递到findById中当作id<association property="parent" column="parent_id" javaType="DeptInfo" select="findById" ></association>
</resultMap>
3…使用虚拟表resultMap进行映射
虚拟表指向主表,标签体指向从表(此处只有一个从表,如有多个就分别指向每一个从表)
使用到的关键属性名 extends 虚拟表是继承哪一个主表
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间,指定接口的完全限定名字-->
<mapper namespace="com.itheima.table02.OneToManyMapper"><!--id:表示接口中方法名resultType:表示方法返回的数据类型,如果返回的是集合,这里指定集合中每个元素类型select标签标签体就是SQL语句--><!--映射class主表--><resultMap id="c" type="Classes"><id property="id" column="cid"/><result property="name" column="cname"/></resultMap><!--映射student从表--><resultMap id="s" type="student" ><id property="id" column="sid"/><result property="name" column="sname"/><result property="age" column="sage"/></resultMap><!--映射两者的虚拟表--><resultMap id="cs" type="Classes" extends="c"><collection property="students" resultMap="s"/></resultMap><select id="selectAll" resultMap="cs">SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid</select>
</mapper>
使用注解查询
没有映射配置文件,仅在核心配置文件mappers标签中指定对应接口即可。
<mappers><mapper class="com.heima.dao.StudentMapper"/> 可扫描单个接口<package name="com.heima.dao"/> 按包扫描
</mappers>
单表查询
可以为@Results 取名字,以复用 使用 id=
value 参数是一个@Result数组,每个@Result对象反应一个列的映射
如果只有一个value属性值,value的名字可以省略
/*** DAO接口*/ public interface UserMapper {/*** 通过id查询用户的方法*/@Select("select * from user where id=#{id}")User findUser(int id);/*** 查询user2的表,其中user_name是对不上属性名的username* @Select 注解用于查询* @Results 注解用于映射不同的列和属性名* 属性:id用来定义这个映射的名字,供其它查询语句重用* 属性:value 参数是一个@Result数组,每个@Result对象一个列的映射* @Result 用来映射每一列* 属性:column 指定列名* 属性:property 指定属性名*/@Select("select * from user2 where id=#{id}")@Results(id = "userMap",value = { @Result(column = "user_name",property = "username") })User findUser2(int id);/*** 查询user2表中所有的记录* @ResultMap 用来引用上面定义的映射*/@Select("select * from user2")@ResultMap("userMap")List<User> findAllUsers2(); }
public interface StudentDao {@Select("select * from student")List<Student> selectAll();@Delete("delete from student where id=#{id}")int delete(Integer id);@insert("插入语句")int a(Student stu);@update("更新语句")int up(Student stu);
}
多表查询
注意:
- @Results({})是一个数组,所以中间每个result要用,隔开
- javaType = Person.class 可以省略
- 主表成员变量与表的字段同名可以不指定,否则必须指定
- 主表的查询语句可以是单表查询也可以是表连接查询,只要最终与预期查询的表结果一致即可
- one/many的取值是根据 主类中的成员变量是集合还是实体类
- 注解中的主类不需要指定,默认以返回值(泛型/实体类)为主类
- 传递的方法不局限于同一个接口中,可以指定对应接口的全限定名.其中的方法调用也行
public interface CardMapper {@Select("select * from card ") @Results({@Result(property = "id",column = "id"),//同名可以不指定@Result(property = "number",column = "number"),//同名可以不指定@Result(property = "p", //主类中的变量名 主要目的是为成员变量P进行封装column = "pid", //往下传递的参数javaType = Person.class, //主类的数据类型one=@One(select = "selectById")) //参数所要传递到的方法处// many=@Many(select = "com.heima.mapper.StudentMapper.selectById")) 调用其他接口中的方法... // 如果有多个方法需要调用,则继续@Result}) public List<Card> selectAll(); //默认以返回值的类型进行封装@Select("select * from person where id=#{id}") //除主表外,其余表都是单表查询以查询对应字段public Person selectById(Integer id);}
延时加载
什么是延迟加载,为什么要使用延迟加载?概念:如果一张表关联了另一张表的数据,只加载这张表中的数据,它关联的另一张表中的数据等到需要用的时候才去加载,称为延迟加载,也叫懒加载。在使用表关联查询的时候,是一次性把所有关联的多张表的数据全部查询出来,并且封装到对象中。如:1对1或1对多。多表关联查询缺点:1. 查询速度更慢2. 更加占用对象的资源,还可能造成资源的浪费。
延迟加载使用指南
- 在映射配置文件中使用,必须使用第二种虚拟表的方式进行映射
前提:一个类中含有多个成员变量且指向不同的类。如果不使用延迟加载则在查询的时候会自动全部查询出来,而不是按需查询
一对一执行流程
一对多执行流程
在注解中使用
首先要开启延迟加载
/**通过id查找用户@Result 用来映射一个列和属性名属性:property 指定另一方在实体类中属性名属性:column 指定为当前表中主键属性:one 表示1对1的关联@One注解:属性:select 指定下一条要执行的SQL语句方法名属性:fetchType 指定是否使用延迟加载 FetchType.LAZY 表示延迟加载 FetchType.EAGER 表示即时加载*/
@Select("select * from user where id=#{id}")
@Results({@Result(property = "userInfo", column = "id", one=@One(select = "findUserInfoById", fetchType = FetchType.LAZY))
})
User findUserById(int id);/**通过id查询用户扩展信息*/
@Select("select * from user_info where id=#{id}")
UserInfo findserInfoById(int id);
开启延迟加载
<settings>
<!--映射下划线为驼峰命名法,会自动将create_time对应createTime-->
<setting name="mapUnderscoreToCamelCase" value="true"/><!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
Mybatis核心类的使用及其执行流程
mybatis的三个核心类
SqlSessionFactoryBuilder 召集工人 build()
SqlSessionFactory(传入输入流) 开始造厂 重量级,极其消耗资源,建议设为静态成员只加载一次 openSession(true)
SqlSession 开始营业
需要在使用前读入核心配置文件
读入核心配置文件的三种方式
//第1种,mybatis提供
InputStream resourceAsStream = Resources.getResourceAsStream("MybatisConfig.xml");
//第2种
InputStream resourceAsStream = 类名.Class.getResourceAsStream("MybatisConfig.xml");
//第3种
InputStream resourceAsStream = 对象名.getClass().getResourceAsStream("MybatisConfig.xml");
//执行mybatis框架对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
XXXDao mapper = sqlSession.getMapper(XXXDao.class);xxx a = mapper.xxx();
//关闭资源sqlSession.close();
mybatis的执行流程
使用框架核心类获取mapper对象—> 读取核心配置文件—>找到映射配置文件
—>mapper对象调用方法—>接口中的对应方法—>配置文件对应方法—>连接数据库进行增删改查—>返回数据
—>配置文件 —>接口中对应的方法 —>mapper调用处
重点
定义接口中的方法,每个方法都必须是唯一的不可重载 返回值类型,传递参数,方法名
映射配置文件中按增删改查功能的不同定义标签,标签中按各个属性,定义接口中的 返回值类型,传递参数,方法名
- 只有查询时,在resultType该属性中才存在返回一个POJO(javaBean[实体类,实体类中的实体类]),其余情况都是int类型
- 接口方法中定义的返回类型是List集合,那么配置文件中不可写list,应该写泛型类型。
- 在传值入SQL语句中的值#{}的赋值标准:基本类可以随便取名;实体类必须是实体类的成员变量名;实体类中的实体类则要用实体类的成员变量名.实体类中实体类的成员变量名
注:
1.由于每次使用mybatis的时候都需要创建mapper对象,可以提取出来作为工具类进行调用。
2.Mybatis会在底层通过接口的全限定名去调用底层方法selectList/One 方法执行对应的每一个方法,所以需要在配置文件中写接口的全限定名
SQL构建器(了解)
使用注解方式时,Myabatis提供一些额外的方式对注解中的SQL语句进行整合
注意:
1.类中必须使用构造代码块进行调用
2.该方法返回值需要使用String接收,同理在SQL()之后要toString 转为字符串格式
3.注解中使用的是XXXProvider(type= XXX.class, method=“对应被调用方法名”)
package com.heima.bean;import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.jdbc.SQL;public class SqlConstracture {public String select(){return new SQL() {{SELECT("*");FROM("student");}}.toString();}public static String delete(){return new SQL(){{DELETE_FROM("STUDENT");WHERE("id=#{id}");}}.toString();}
}
public interface StudentDao {// @Select("select * from student")@SelectProvider(type =SqlConstracture.class,method ="select" )List<Student> selectAll();// @Delete("delete from student where id=#{id}")@DeleteProvider(type = SqlConstracture.class,method ="delete" )int delete(@Param("id") Integer id);}
小结
- 当类成员变量与字段名相同时,注解不需要指定,配置文件需要指定
如遇到驼峰命名和字段名_重合时,可以在settings里开启核心配置文件中的mapUnderscoreToCamelCase 来自动映射
- 接口的方法不可以重载(方法唯一)
动态SQL
在需要使用动态SQL的时候,都在配置文件中执行,如果使用注解的方式写动态SQL没有可读性
提供动态SQL的标签有
if,where,set,foreach,(sql,include),(trim),(choose,otherwise,when)
不是独立关系,可以进行相互匹配只要逻辑上行得通即可,使用了这个标签就不需要再写对应位置上的关键字
if和where标签
if标签的作用: 对传递进来的数据进行逻辑判断,如果符合条件就进行拼接
where标签的作用:在where标签中进行多个if标签的判断的时候,可以自动去除在语句中存在的多余的and or where关键字
需要注意的是只能自动去除,不会自动添加,少了关键字依然报错
为了增强代码的健壮性,所有if判断的前面都添加对应关键字
<select id="selectCondition" resultType="student" parameterType="student">select * from student<where><if test="id != null">and id = #{id}</if><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>
set标签
set标签的作用:主要用于update标签,来设置set关键字,以去除更新多个语句时多余存在的 ,
为了增强代码的健壮性,所有if判断的前面都添加
<!--
1. 如果username属性不为空,则更新这个字段。
2. 如果sex不为空则更新这个字段
-->
<update id="updateUser">update user<set><if test="username!=null and username!=''">username=#{username},</if><if test="sex!=null and sex!=''">sex=#{sex},</if></set>where id=#{id}
</update>
foreach标签
foreach标签的作用:对传入进来的参数进行遍历循环再输出到指定的位置上
foreach标签的属性 | 作用:用于遍历集合或数组,多次拼接生成SQL语句 |
---|---|
collection | 取值:如果是集合使用list,如果是数组使用array |
item | 代表集合中每个元素,给元素定义一个名字 |
separator | 每次遍历完成以后添加1个分隔符 |
#{变量名.属性} | 标签体中引用的时候,必须使用变量名.属性名 |
总共有三种遍历对象: 集合,数组,要遍历集合或者数组在某个resultMap或者JavaBea中
parameterType为集合
<!--使用foreach标签遍历一个集合,生成SQL语句--> <insert id="addUsers" >insert into user (username, birthday, sex, address) values<!--foreach标签的属性:collection 指定是集合还是数组,如果是集合使用list,如果是数组使用arrayitem 表示集合中每个元素的变量名,这里是一个实体类对象separator 每次拼接完以后,添加1个符号分隔--><foreach collection="list" item="user" separator=",">(#{user.username},#{user.birthday},#{user.sex},#{user.address})</foreach> </insert>
parameterType为数组
<!--批量删除用户 --> <delete id="deleteUsers">delete from user where id in<!--collection: 表示要遍历的集合类型,数组使用arrayitem: 表示其中每个元素的变量名open:表示循环开始前添加一个符号,只会执行1次separator 每次拼接完以后,添加1个符号分隔,执行多次close:表示循环结果以后添加一个符号,只会执行1次--><foreach collection="array" open="(" item="id" separator="," close=")">#{id}</foreach> </delete>
parameterType为JavaBean ,成员变量为数组/集合
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo"><include refid="defaultUser"></include><where><if test="ids != null and ids.size()>0"><foreach collection="ids" open="and id in (" item="uid" separator="," close=")">#{uid}</foreach></if></where> </select>
注意: 当传入的集合或者数组里面存放的是一个一个的实体类的时候,在调用时必须使用#{实体类。成员变量}赋值。
sql和include标签
sql标签的作用:提取重复代码到此标签中,
include标签的作用:在对应处通过refid调用sql标签id名
优缺点:在使用的时候会大大简化代码的编写,但是极其影响代码可读性,慎用。
<!--
1. sql标签定义查询条件代码块Map = {minDate='1999-9-9', maxDate='2000-1-1'}在XML中<或>符号是有特殊含义的,通常不能直接写在XML中有两种解决方案:1) 使用转义:< >2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析-->
<sql id="sqlCondition"><where><if test="minDate!=null and minDate!=''">birthday >= #{minDate}</if><if test="maxDate!=null and maxDate!=''"><![CDATA[and birthday <= #{maxDate}]]></if></where>
</sql><!--
include标签的作用:引用使用sql标签定义的代码段属性:refid对应上面sql的id值
-->
<select id="findUsersByMap" resultType="user">select * from user <include refid="sqlCondition"/>
</select><select id="findCountByMap" resultType="int">select count(*) from user <include refid="sqlCondition"/>
</select>
分页插件
PageHelper 可以自动在底层实现分页的效果,是一个三方jar包
分页原理:
package com.itheima.entity;import java.util.List;/*** 封装页面所有属性的实体对象* 使用泛型以后可以通用*/
public class PageBean<T> {//第1组:可以从数据库中查询到的private List<T> data; //这一页的数据private int count; //一共有多少条记录//第2组:由用户在浏览器端提供的数据private int current; //当前第几页private int size; //每页大小//第3组:由其它的属性计算出来,写在get方法中private int first; //第一页private int previous; //上一页private int next; //下一页private int total; //总页数=最后一页public List<T> getData() {return data;}public void setData(List<T> data) {this.data = data;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}public int getCurrent() {return current;}public void setCurrent(int current) {this.current = current;}public int getSize() {return size;}public void setSize(int size) {this.size = size;}//计算第1页public int getFirst() {return 1; //直接返回1}public void setFirst(int first) {this.first = first;}//计算上一页public int getPrevious() {//如果当前页大于1,上一页=当前页-1,否则返回1if (getCurrent() > 1) {return getCurrent() - 1;}else {return 1;}}public void setPrevious(int previous) {this.previous = previous;}//计算下一页public int getNext() {//如果当前页小于最后一页,下一页=当前页+1,否则返回最后一页if (getCurrent() < getTotal()) {return getCurrent() + 1;}else {return getTotal();}}public void setNext(int next) {this.next = next;}//计算总页数 = 总记录数 / 页面大小public int getTotal() {//如果能整除就正好是这么多页,不能整除就加1if (getCount() % getSize() == 0) {return getCount() / getSize();}else {//在java中整数相除得到整数return getCount() / getSize() + 1;}}public void setTotal(int total) {this.total = total;}//注:因为有四个属性是在get方法中计算的,如果直接输出属性值,不一定准确,应该输出get方法才能看到正确的结果@Overridepublic String toString() {return "PageBean{" +"data=" + getData() +", count=" + getCount() +", current=" + getCurrent() +", size=" + getSize() +", first=" + getFirst() +", previous=" + getPrevious() +", next=" + getNext() +", total=" + getTotal() +'}';}
}
主要针对所有的数据库进行分页
步骤:
导包
在核心配置文件中设置plugins
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
调用 PageHelper相关方法
PageHelper.startPage(第几页,显示的个数); 分页显示,循环显示时仅显示指定个数,使用的位置要放在查询之前
PageInfo info = new PageInfo<>(调用方法的结果集); 用于调用显示上一页等操作
package com.itheima.paging;import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.itheima.bean.Student; import com.itheima.mapper.StudentMapper; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test;import java.io.InputStream; import java.util.List;public class Test01 {@Testpublic void selectPaging() throws Exception{//1.加载核心配置文件InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");//2.获取SqlSession工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);//3.通过工厂对象获取SqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession(true);//4.获取StudentMapper接口的实现类对象StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);//通过分页助手来实现分页功能// 第一页:显示3条数据//PageHelper.startPage(1,3);// 第二页:显示3条数据//PageHelper.startPage(2,3);// 第三页:显示3条数据PageHelper.startPage(3,3);//5.调用实现类的方法,接收结果List<Student> list = mapper.selectAll();//6.处理结果for (Student student : list) {System.out.println(student);}//获取分页相关参数PageInfo<Student> info = new PageInfo<>(list);System.out.println("总条数:" + info.getTotal());System.out.println("总页数:" + info.getPages());System.out.println("当前页:" + info.getPageNum());System.out.println("每页显示条数:" + info.getPageSize());System.out.println("上一页:" + info.getPrePage());System.out.println("下一页:" + info.getNextPage());System.out.println("是否是第一页:" + info.isIsFirstPage());System.out.println("是否是最后一页:" + info.isIsLastPage());//7.释放资源sqlSession.close();is.close();} }
Mybatis底层原理分析
此处去除所有安全判断之后,经过JDBC的底层,以注解为例分析可得:
这是JDBC底层调用反射框架时的参数
@Testpublic void queryForObject() {//查询一条记录并封装自定义对象的测试String sql = "SELECT * FROM student WHERE sid=?";Student stu = template.queryForObject(sql,new BeanHandler<>(Student.class),1);System.out.println(stu);}
由此可得:需要用到SQL语句,对应接口的字节码对象,传入的形参。
- 当创建mapper实体对象的时候要读入核心配置文件,此时可以得到mappers标签中通过指定的配置文件可以得到所有接口的字节码对象(映射配置文件中有接口的全限定名)
- 接口的方法处可以获取到SQL语句,传入的形参和返回值对象(查看使用多对象list还是单对象实例或聚合函数)
- 通过验证使用selectList()/selectOne()在底层判断后,调用对应的单,多,聚合的反射方法。传入上述参数。
- 到达核心框架处,经过反射和元数据的判断后,执行SQL语句,把结果集对应封装进返回值对象(可以理解为使用了BeanUtils的populate方法),返回调用处
- 一一返回,到达调用处,结果框架调用。
其他零散知识点
1.Data类中提供方法 valueOf(日期字符串) 可以将日期格式的字符串转为对应的Data类 以对应数据库中的Data属性
new User(null,“白龙马”,Date.valueOf(“2019-05-01”),“男”,“东海龙宫”);
2.在XML中<或>符号是有特殊含义的,通常不能直接写在XML中
有两种解决方案:
1) 使用转义:< >
2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析<![CDATA[and birthday <= #{maxDate}]]>
3.使用注解的方式,如果方法中的形参不使用map集合进行传递,那么多于一个形参时,使用@Param关键字
如下:
@Select("select * from student where id=#{id} and name=#{name}")
List<Student> selectAll(@Param("id") Integer id,@Param("name") String name);
总结
- 核心配置文件的标签必须从上到下按顺序定义,否则报错
- 想要注解和配置文件mappers的子标签通用,必须接口与映射文件同名且在同一包下 class属性用.隔开,resource用/隔开
- 事务自动提交需要自己手动设置
- 核心配置文件中 resultMap有两种格式,单个map更为简单,虚拟表形式主要用于延迟加载时使用
- 对#{}的赋值,基本类名字随意,实体类是变量名,实体类中的实体类是实体类变量名.其成员变量名
tis底层原理分析
此处去除所有安全判断之后,经过JDBC的底层,以注解为例分析可得:
这是JDBC底层调用反射框架时的参数
@Testpublic void queryForObject() {//查询一条记录并封装自定义对象的测试String sql = "SELECT * FROM student WHERE sid=?";Student stu = template.queryForObject(sql,new BeanHandler<>(Student.class),1);System.out.println(stu);}
由此可得:需要用到SQL语句,对应接口的字节码对象,传入的形参。
- 当创建mapper实体对象的时候要读入核心配置文件,此时可以得到mappers标签中通过指定的配置文件可以得到所有接口的字节码对象(映射配置文件中有接口的全限定名)
- 接口的方法处可以获取到SQL语句,传入的形参和返回值对象(查看使用多对象list还是单对象实例或聚合函数)
- 通过验证使用selectList()/selectOne()在底层判断后,调用对应的单,多,聚合的反射方法。传入上述参数。
- 到达核心框架处,经过反射和元数据的判断后,执行SQL语句,把结果集对应封装进返回值对象(可以理解为使用了BeanUtils的populate方法),返回调用处
- 一一返回,到达调用处,结果框架调用。
其他零散知识点
1.Data类中提供方法 valueOf(日期字符串) 可以将日期格式的字符串转为对应的Data类 以对应数据库中的Data属性
new User(null,“白龙马”,Date.valueOf(“2019-05-01”),“男”,“东海龙宫”);
2.在XML中<或>符号是有特殊含义的,通常不能直接写在XML中
有两种解决方案:
1) 使用转义:< >
2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析<![CDATA[and birthday <= #{maxDate}]]>
3.使用注解的方式,如果方法中的形参不使用map集合进行传递,那么多于一个形参时,使用@Param关键字
如下:
@Select("select * from student where id=#{id} and name=#{name}")
List<Student> selectAll(@Param("id") Integer id,@Param("name") String name);
总结
- 核心配置文件的标签必须从上到下按顺序定义,否则报错
- 想要注解和配置文件mappers的子标签通用,必须接口与映射文件同名且在同一包下 class属性用.隔开,resource用/隔开
- 事务自动提交需要自己手动设置
- 核心配置文件中 resultMap有两种格式,单个map更为简单,虚拟表形式主要用于延迟加载时使用
- 对#{}的赋值,基本类名字随意,实体类是变量名,实体类中的实体类是实体类变量名.其成员变量名
- 用到动态SQL的时候都要用配置文件的形式,便于可读性
Mybatis使用及原理相关推荐
- mybatis的工作原理
MyBatis 的工作原理 在学习 MyBatis 程序之前,读者需要了解一下 MyBatis 工作原理,以便于理解程序.MyBatis 的工作原理如图 2 所示. 下面对图 2 中的每步流程进行说明 ...
- Mybatis简介与原理
转载自 Mybatis简介与原理 什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到 ...
- MyBatis中的原理
01Mybatis的运行原理 运行过程中涉及到的类 1.1 Resources MyBatis中IO流的工具类 1.1 加载配置文件 1.2 SqlSessionFactoryBuilder() 构建 ...
- MyBatis整合Spring原理分析
目录 MyBatis整合Spring原理分析 MapperScan的秘密 简单总结 假如不结合Spring框架,我们使用MyBatis时的一个典型使用方式如下: public class UserDa ...
- 事务-2 Spring与Mybatis事务实现原理
背景: 本文承接事务-1 事务隔离级别和Spring事务传播机制,是事务专题的第二篇:主题内容是Mybatis和Spring事务原理,结合源码和案例进行介绍. 本文主题内容为事务原理, 尤重Sprin ...
- MyBatis动态代理原理
1. MyBatis核心组件 在详细探究MyBatis中动态代理机制之前,先来补充一下基础知识,认识一下MyBatis的核心组件. SqlSessionFactoryBuilder(构造器): 它可以 ...
- MyBatis基本工作原理介绍
1.MyBatis基本工作原理介绍 计算机的基本工作就是存储和计算,而MyBatis是存储领域的利器.MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结 ...
- Mybatis 的工作原理及流程
1.介绍 MyBatis的底层操作封装了JDBC的API,MyBatis的工作原理以及核心流程与JDBC的使用步骤一脉相承,MyBatis的核心对象(SqlSession,Executor)与JDBC ...
- Mybatis Dynamic SQL原理——更优雅的使用Mybatis
Mybatis Dynamic SQL原理 一.Mybatis Dynamic Sql是什么 二.Mybatis Dynamic Sql原理 2.1 mybatis的本质是什么 2.2 mapper接 ...
- 12.mybatis核心架构原理
课程标题<2022超详细mybatis框架源码解读> 课程内容: 1.mybatis环境搭建 2.mybatis使用哪些设计模式 3.mybatis大致运行原理 4.mybatis建造者模 ...
最新文章
- pycharm与python环境配置
- RabbitMQ消息幂等性问题
- 【人工智能】AI如何把招人效率提高四成
- mysql 半同步 5.6及5.7
- 细说WCF中的会话模式
- 假如有Thread1、Thread2、ThreaD3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?
- php根据下标倒序排,PHP的三种排序方式
- Java设计模式(7)——装饰者模式
- 【有限差分法】(一)有限差分法的基本流程与常用格式
- TikTok广告投放必备指南
- win10装inventor哪个版本_超详细!高效电脑装系统教程
- 雷电模拟器连接hb_原生的安卓模拟器来了,微软发布Your Phone,与三星独家合作...
- 中值定理5-泰勒中值定理
- (二)大话深度学习编译器中的自动调优·DSL与IR
- 吃饭 睡觉 打豆豆!!!
- js中字符串截取函数及其方法
- 基于B/S的校园餐厅网上订餐系统
- 苹果屏保壁纸_神经病手势时钟-手势数字时钟app下载android安卓版ios苹果版
- tkinter style样式使用
- kafka可靠性保证
热门文章
- vue核心面试题:v-for中为什么要用key
- 什么是jQuery,jQuery的特点。
- 【Linux】SSH相关命令
- python程序设计是什么专业-那门用Python讲授的程序设计课程能带给学生什么?
- 训练ChatGPT的必备资源:语料、模型和代码库完全指南
- Linux 命令(217)—— iptables-restore 命令
- 保定学院数学与计算机系2016,保定学院数学与计算机系
- Linux常用命令(适合初学者)
- python可以用保留字作为函数的名字吗_Python不允许使用关键字作为变量名,允许使用内置函数名作为变量名,但这会改变函数名的含义...
- redux及react-redux