Mybatis
Mybatis的介绍
mybatis是Apache软件基金会下的一个开源项目,前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下,改名为mybatis。2013年11月又迁移到了github。
是持久层框架,用来访问数据库,只是众多持久层框架中一个,也是ORM框架:对象关系映射框架。
mybatis的优点
- 简单易学:mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个SQL映射文件即可。
- 使用灵活:Mybatis不会对应用程序或者数据库的现有设计强加任何影响。SQL语句写在XML里,便于统一管理和优化。
- 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易进行单元测试。SQL语句和代码的分离,提高了可维护性。
mybatis的不足
- 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
- SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
- 框架还是比较简陋,功能尚有缺失,二级缓存机制不佳
ORM映射机制(Object Relational Mapping 对应关系映射)
什么是ORM?
将关系型数据库映射成面向对象编程的语言,如果使用框架,这由框架去完成。
mybatis主要采用的技术是映射,通过映射的方式传递信息由框架进行解析,然后与数据库进行交互最后完成对数据集的持久化操作。
Mybatis的两种映射方式
java与数据库表所对应的关系:
类 -------> 表结构
对象------>记录
类成员---->字段名
1.XML配置文件
核心配置文件
核心文件本质上在mybatis的底层会创建出一个configuration的实体类,该配置文件有很多标签,各个标签的位置不可变,必须从上到下排列,可以省略。下列仅列举常用标签顺序
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
有两种方式进行加载映射配置文件,在此标签下可以对多个包或者文件进行加载
<!-- mappers引入映射配置文件 -->
<mappers>
<!--
子元素:mapper
属性:
resource:加载类路径下指定的配置文件,注:分隔符是/,而不是点号
url: 读取指定路径下配置文件,或者网络的配置文件
<mapper url="file:///d:/UserMapper.xml"/>
class: 指定接口的完全限定名,用于注解的配置,不需要XML文件。
<mapper class="com.itheima.dao.UserMapper"/>
子元素:package
1. 指定扫描哪个包下所有的DAO接口,如果使用这种写法,接口名与配置文件名字要相同。
如:接口名UserMapper.java 配置文件名:UserMapper.xml
2. 接口与配置文件必须放在同一个包下
-->
<!-- 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的填写
-
简单类 八大基本类型具体书写格式可参照typeAliases的别名规范
-
POJO类(javaBean)
-
实体类
自己写的单一类 例如User,Student等,主要用于单表查询
当传入的是实体类的时候,必使用实体类的名字而不是随意乱写
-
实体类中含实体类
自己写的类grade中含有多个其他类作为成员变量存在 如:user ,string ,int
此时若传入类型resultType=grade ,那么在sql语句中应该用user.user的成员变量来进行调用
如:select * from student where name like ‘%’ #{user.name} ‘’%’
-
映射文件的两种输出类型
主要针对resultType的填写
- 简单类 八大基本类型
- 实体类 在接口处使用集合来进行封装的时候 ,配置文件中填写泛型的类型,底层自动封装为集合返回
小结
-
接口与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);
-
-
子标签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
因为该属性的作用是将结果集直接封装到对应的jabaBean中去
使用任何一种查询,只要使用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>courses
//下面是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>
2.使用虚拟表resultMap进行映射
虚拟表指向主表,标签体指向从表(此处只有一个从表,如有多个就分别指向每一个从表)
使用到的关键属性名 extends 虚拟表是继承哪一个主表
<?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">
<!--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对多。
多表关联查询缺点:
-
查询速度更慢
-
更加占用对象的资源,还可能造成资源的浪费。
延迟加载使用指南
- 在映射配置文件中使用,必须使用第二种虚拟表的方式进行映射
前提:一个类中含有多个成员变量且指向不同的类。如果不使用延迟加载则在查询的时候会自动全部查询出来,而不是按需查询
一对一执行流程
一对多执行流程
-
在注解中使用
首先要开启延迟加载
/**
通过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");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
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
不是独立关系,可以进行相互匹配只要逻辑上行得通即可,使用了这个标签就不需要再写对应位置上的关键字
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,如果是数组使用array item 表示集合中每个元素的变量名,这里是一个实体类对象 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: 表示要遍历的集合类型,数组使用array item: 表示其中每个元素的变量名 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,否则返回1
if (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() {
//如果能整除就正好是这么多页,不能整除就加1
if (getCount() % getSize() == 0) {
return getCount() / getSize();
}
else {
//在java中整数相除得到整数
return getCount() / getSize() + 1;
}
}
public void setTotal(int total) {
this.total = total;
}
//注:因为有四个属性是在get方法中计算的,如果直接输出属性值,不一定准确,应该输出get方法才能看到正确的结果
@Override
public 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 { @Test public 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底层调用反射框架时的参数
@Test
public 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);
由此可得:需要用到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的时候都要用配置文件的形式,便于可读性