START
- WeChat Applet :Java空巷
Mybatis
推荐一个学习地方(http://c.biancheng.net/view/4302.html)
Hibernate 和 MyBatis 的区别
- sql 优化方面
- Hibernate 不需要编写大量的 SQL,就可以完全映射,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作。但会多消耗性能。
- MyBatis 手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对大些。
- 开发方面
- MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系。
- Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。
- Hibernate 优势
- Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
- Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
- Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
- Mybatis优势
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- MyBatis 容易掌握,而 Hibernate 门槛较高
Mybatis简介
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache
software foundation 迁移到了google code,并且改名为MyBatis 。
2013年11月迁移到Github。
作用
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
聊聊持久化
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)
就是把数据保存到可以永久保持的存储设备当中。一du般来说,持久更为直接的理解就是对数据库的各种操作,如CRUD(增加,删除,修改,查询),更新等操作
持久化是就是把数据存在磁盘而不是内存。
- 程序产生的数据首先都是存在内存。
- 内存是不可靠的,一断电数据就没得啦。。。
- 那可靠的存储地方是硬盘,U盘,光盘等等。
- 我们的程序在运行时说的持久化通常就是只将内存的数据存在硬盘。
高级一点:持久化就是将数据从瞬时态转化为持久态,长久保存。
聊聊持久层
持久层,就是把持久的动作封装成一个独立的层,这是为了降低功能代码之间的关联。创建一个更清晰的抽象,提高代码的内聚力,降低代码的耦合度,从而增强代码的要劳动局生和可重用性。
聊聊持久层框架
实现持久层的框架有: JDBC, Hibernate,Mybatis,JPA等技术
写过JDBC没有?你会发现(里头的重复代码太多了),当你一个表里有很多很多的字段时,你的查询,新增等等 语句是不是得写起来格外的不舒服。
特点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
总体流程
- 加载配置并初始化
触发条件:加载配置文件
处理过程:将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
- 接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
- 处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
©获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源。
(4)返回处理结果将最终的处理结果返回。
MyBatis功能架构
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
MyBatis优缺点
- sql语句与代码分离,存放于xml配置文件中:
优点:便于维护管理,不用在java代码中找这些语句;
缺点: JDBC方式可以用打断点的方式调试,但是Mybatis不能,需要通过log4j日志输出日志信息帮助调试,然后在配置文件中修改。
- 用逻辑标签控制动态SQL的拼接:
优点:用标签代替编写逻辑代码;
缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂的语句。
- 查询的结果集与java对象自动映射:
优点:保证名称相同,配置好映射关系即可自动映射或者,不配置映射关系,通过配置列名=字段名也可完成自动映射。
缺点:对开发人员所写的SQL依赖很强
- 编写原声SQL
优点:接近JDBC,比较灵活。
缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如mysql数据库编程Oracle数据库,部分的sql语句需要整。
- 最重要的一点,使用的人多!公司需要!但是应为用了反射,效率会下降,所有有些公司会使用原生的jdbc
来 上代码
搭个数据库
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`gender` varchar(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
maven配置(pom.xml 引入依赖)
<dependencies>
<dependency>
<!--lombok-->
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<!-- mybatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 数据库确定 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
<!-- 处理资源被过滤问题 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
编写实体类
/**
* @author 空巷
* @Date 2020/5/28
*/
//使用了lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private int age;
private String gender;
}
MyBatis核心配置文件(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 default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sys?
useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
配置文件解读(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></properties>
<!-- 设置 (mybatis最为复杂的配置也是最重要的,会改变mybatis运行时候的行为) -->
<settings>
<!-- 具体的参数名和参数值 -->
<setting name="" value=""/>
</settings>
<!-- 类型命名 (在TypeAliasRegistry中可以看到mybatis提供了许多的系统别名)-->
<typeAliases></typeAliases>
<!-- 类型处理器 (比如在预处理语句中设置一个参数或者从结果集中获取一个参数时候,都会用到类型处理器,在TypeHandlerRegistry中定义了很多的类型处理器)-->
<typeHandlers></typeHandlers>
<!-- 对象工厂(myabtis在构建一个结果返回的时候,会使用一个ObjectFactory去构建pojo) -->
<objectFactory type=""></objectFactory>
<!-- 插件:mybatis的插件,插件可以修改mybatis的内部运行规则 -->
<plugins>
<plugin interceptor=""></plugin>
</plugins>
<!-- 环境:配置mybatis的环境 -->
<environments default="">
<!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 -->
<environment id="">
<!-- 事务管理器 -->
<transactionManager type=""></transactionManager>
<!-- 数据源 -->
<dataSource type=""></dataSource>
</environment>
</environments>
<!-- 数据库厂商标识 -->
<databaseIdProvider type=""></databaseIdProvider>
<!-- 映射器:指定映射文件或者映射类 -->
<mappers></mappers>
</configuration>
settings元素
在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。settings 配置项说明,如表 1 所示。
配置项 | 作用 | 配置选项 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响所有映射器中配置缓存的全局开关 | true|false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 | true|false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载 | true|felse | 版本3.4.1 (不包含) 之前 true,之后 false |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动) | true|false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果 | true|false | true |
useGeneratedKeys | 允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby) | true|false | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射。 PARTIAL 表示只会自动映射,没有定义嵌套结果集和映射结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套) | NONE、PARTIAL、FULL | PARTIAL |
autoMappingUnkno wnColumnBehavior | 指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常 | NONE、WARNING、FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新 | SIMPLE、REUSE、BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数 | 任何正整数 | Not Set (null) |
defaultFetchSize | 设置数据库驱动程序默认返回的条数限制,此参数可以重新设置 | 任何正整数 | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 false | true|false | false |
safeResultHandlerEnabled | 允许在嵌套语句中使用分页(ResultHandler)。如果允许,设置false | true|false | true |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射 | true|false | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据 | SESSION|STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER | NULL、VARCHAR、OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载 | — | equals、clone、hashCode、toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言 | — | org.apache.ibatis .script.ing.xmltags .XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 时,是否调用映射对象的 setter(map 对象时为 put)方法,这对于 Map.kcySet() 依赖或 null 值初始化时是有用的。注意,基本类型(int、boolean 等)不能设置成 null | true|false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀 | 任何字符串 | Not set |
loglmpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动査找 | SLF4J|LOG4J|LOG4J2|JDK_LOGGING |COMMONS_LOGGING |ST DOUT_LOGGING|NO_LOGGING | Not set |
proxyFactory | 指定 MyBatis 创建具有延迟加栽能力的对象所用到的代理工具 | CGLIB|JAVASSIST | JAVASSIST (MyBatis 版本为 3.3 及以上的) |
vfsImpl | 指定 VFS 的实现类 | 提供 VFS 类的全限定名,如果存在多个,可以使用逗号分隔 | Not set |
useActualParamName | 允许用方法参数中声明的实际名称引用参数。要使用此功能,项目必须被编译为 Java 8 参数的选择。(从版本 3.4.1 开始可以使用) | true|false | true |
<!--settings能对我的一些核心功能进行配置,如懒加载、日志实现、缓存开启关闭等-->
<settings>
<!--标准输出日志
<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<!--设置日志实现-->
<setting name="logImpl" value="LOG4J"/>
<!--开启二级缓存-->
<!--<setting name="cacheEnabled" value="true"/>-->
<!--指定哪个对象的方法触发一次延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--是否允许单一语句返回多结果集(需要兼容驱动)-->
<setting name="multipleResultSetsEnabled" value="true"/>
<!--使用列标签代替列名。-->
<setting name="useColumnLabel" value="true"/>
<!--允许JDBC 支持自动生成主键,需要驱动兼容。。-->
<setting name="useGeneratedKeys" value="false"/>
<!--指定 MyBatis 应如何自动映射列到字段或属性。-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!--指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常。-->
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<!--配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新 。-->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!--设置超时时间,它决定驱动等待数据库响应的秒数-->
<setting name="defaultStatementTimeout" value="25"/>
<!--设置数据库驱动程序默认返回的条数限制,此参数可以重新设置-->
<setting name="defaultFetchSize" value="100"/>
<!--允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 false-->
<setting name="safeRowBoundsEnabled" value="false"/>
<!--是否开启自动驼峰命名规则映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!--MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。-->
<setting name="localCacheScope" value="SESSION"/>
<!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。-->
<setting name="jdbcTypeForNull" value="OTHER"/>
<!--指定哪个对象的方法触发一次延迟加载-->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
environments元素
在 MyBatis 中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言只需要配置其中的一个就可以了。
它下面又分为两个可配置的元素:事务管理器(transactionManager)、数据源(dataSource)。
在实际的工作中,大部分情况下会采用 Spring 对数据源和数据库的事务进行管理,这些我们教程后面都会进行讲解。本节我们会探讨 MyBatis 自身实现的类。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sys?
useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
mappers元素
mappers的存在就是要对写好的mapper和xml进行统一管理
<mappers>
<mapper resource="mapper/userMapper.xml"/>
<mapper resource="mapper/GradeMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
<mapper class="com.xinzhi.dao.AdminMapper"/>
</mappers>
CRUD练练手
先来说说mybatis的内置别名(下面会用到)
别名 | 映射的类型 |
---|---|
_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 |
给自己的类设置别名
在核心配置文件(mybatis-config.xml)中加入
<typeAliases>
<!--为 com.xinzhi.entity.User 这个类设置别名user-->
<!--<typeAlias type="com.xinzhi.entity.User" alias="user"/>-->
<!--为com.xinzhi.entity 包下的所有文件设置别名 -->
<package name="com.xinzhi.entity"/>
</typeAliases>
标签中 有 type 和 alias 两个属性
type 填写 实体类的全类名, alias 可以不填,不填的话,默认是类名,不区分大小写,
alias 填了的话就以 alias里的值为准。
标签 为某个包下的所有类起别名; name 属性填写包名。 别名默认是类名,不区分大小写。
select(查)
dao层写个接口类(UserMapper)
/**
* @author 空巷
* @Date 2020/5/28
*/
public interface UserMapper {
/**
* 通过id查询用户
* @return
*/
User selectUserById(int id);
}
eg:
一个接口类Mapper (就是Dao)不需要实现
一个接口对应一个xml文件
两个文件名字最好一致
当你建了一个 xxxMapper.xml 配置文件后,需要去mybatis 核心配置文件中注册
<?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 default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sys?
useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/userMapper.xml"/>
</mappers>
</configuration>
xxxMapper.xml配置文件格式
<?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="com.xinzhi.dao.UserMapper">
<select id="selectUserById" resultType="User">
select id,name,age,gender from user where id = #{id}
</select>
</mapper>
请注意:
- resultType:指定返回类型,设置返回类型
- parameterType:参数类型,设置查询参数类型
- id:指定对应的方法,指定这个sql语句对应哪个dao层的方法
- #{id}:sql中的变量,要保证大括号的变量必须在User对象里也有
- #{ }:占位符,其实就是之前的 PreparedStatement
#{ }和 ${ }有啥区别
- #{ }的作用主要是替换编译语句的 PrepareStatement 中的占位符
- ${ }的作用数据集直接进行字符串替换
测试:
**eg:**当需要测试时需要加个头和尾 当增删查改时,都需要添加事务(我直接运用了junit测试类 避免代码重复 下面有解释)
private SqlSession session;
@Before
public void before() {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
}
@After
public void after() {
//统一提交事务
session.commit();
session.close();
}
//通过id查询用户
@org.junit.Test
public void selectUserById() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectUserById(5);
System.out.println(user);
}
insert(增)
接口类
/**
* 添加用户
* @param user
* @return
*/
int addUser(User user);
UserMapper.xml
<insert id="addUser" parameterType="User">
insert into user(`name`,age,gender) VALUES (#{name},#{age},#{gender})
</insert>
测试:
//添加用户
@org.junit.Test
public void addUser() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User(10,"aaa",20,"男");
int i = userMapper.addUser(user);
System.out.println(i);
}
update(改)
接口类:
/**
* 这是用通过id修改用户的方法
* @param user
* @return
*/
int updateUser(User user);
UserMapper.xml
<update id="updateUser" parameterType="User">
UPDATE `user` name = #{name}, age = #{age}, gender = #{gender} WHERE id = #{id}
</update>
测试:
//通过id修改用户
@org.junit.Test
public void updateUserById(){
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = new User();
user.setId(4);
user.setName("zxcjlk");
user.setAge(20);
user.setGender("男");
userMapper.updateUser(user);
}
delete(删)
接口类:
/**
* 根据id删除用户
* @param id
* @return
*/
int deleteUser(int id);
UserMapper.xml
<delete id="deleteUser" parameterType="int">
DELETE FROM `user` WHERE id = #{id}
</delete>
测试:
//删除用户
@org.junit.Test
public void deleteUser(){
UserMapper userMapper = session.getMapper(UserMapper.class);
int i = userMapper.deleteUser(6);
System.out.println(i);
}
整理一下:
-
SqlSessionFactoryBuilder().build(inputStream)-读取核心配置文件,
-
核心配置文件负责核心配置,重要的一点是通过核心配置找到Mapper的xml文件。
-
xml文件通过 namespace 又能找到 对应的接口文件。
-
有了方法,有了sql,会用动态代理,生成代理类,实现对象的方法。
-
session.getMapper(UserMapper.class) 获取的就是代理对象,当然就有了对应的实现。
-
结论多个参数时,使用@Param注解,能制定对应的参数,当然如果能封装对象最好封装对象
例如:
/**
* 新增用户
* @param userId
* @param name
* @param pws
* @return
*/
int addUser(@Param("id") int id,@Param("username") String name,@Param("password")
String password);
模糊查询
方案一:在Java代码中拼串
string name = “%IT%”;
list<name> names = mapper.getUserByName(name);
<select id=”getUsersByName”>
select * from user where name like #{name}
</select>
方案二:在配置文件中拼接
<select id=”getUsersByName”>
select * from user where name like "%"#{name}"%"
</select>
map的使用
map可以替代任何实体类,当数据比较复杂时,可以适当考虑使用map来完成相关的工作
接口类
/**
* 使用根据用户查询用户
* @param name
* @return
*/
List<User> getUsersByParams(Map<String,Object> name);
UserMapper.xml
<!--map的使用-->
<select id="getUsersByParams" resultType="map">
select id,name,age,gender from user where name = #{name}
</select>
测试:
//使用map
@org.junit.Test
public void getUsersByParams(){
UserMapper userMapper = session.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("name","张三");
map.put("id",1);
List<User> usersByParams = userMapper.getUsersByParams(map);
System.out.println(usersByParams);
}
resultMap
当数据库的字段名和变量名不一致时 例如 数据库里 username 变量名 name
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id; //id
private String name; //姓名,数据库为username
private String password; //密码,一致
}
mapper
//根据id查询用户
User selectUserById(int id);
UserMapper.xml
<select id="selectUserById" resultType="user">
select * from user where id = #{id}
</select>
测试:
@Test
public void testSelectUserById() {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
结果发现:
查询出来 name 为 null 说明
mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 利用反射去对应的实体类中查找相应列名的set方法设值 ,肯定找不到username;
解决方案一:
可以想想,是不是只需要改变数据库的字段名使得和变量名一致即可
<select id="selectUserById" resultType="User">
select id , username as name ,password from user where id = #{id}
</select>
解决方案二:【推荐使用】
使用结果集映射 ResultMap
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="username" property="name"/>
<result column="password" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , username , password from user where id = #{id}
</select>
使用注解开发
我这里直接就写代码了 很简单,通过代码来感受
接口中加入注解:
/**
* @author 空巷
* @Date 2020/5/28
*/
public interface AdminMapper {
/**
* 通过id查询用户
* @return
*/
@Select("select id,name,age,gender from admin where id = #{id}")
Admin selectAdminById(int id);
/**
* 添加用户
* @param Admin
* @return
*/
@Insert("insert into admin(`name`,age,gender) VALUES (#{name},#{age},#{gender})")
int addAdmin(Admin Admin);
/**
* 这是用通过id修改用户的方法
* @param Admin
* @return
*/
@Update("UPDATE `admin` SET `name` = #{name},age = #{age},gender = #{gender} WHERE id = #{id}")
int updateAdmin(Admin Admin);
/**
* 根据id删除用户
* @param id
* @return
*/
@Delete("DELETE FROM `admin` WHERE id = #{id}")
int deleteAdmin(int id);
/**
* 根据用户名查询用户
* @param name
* @return
*/
@Select("select id,name,age,gender from admin where name like #{name}")
List<Admin> getAdminByName(String name);
/**
* 使用根据用户查询用户
* @param name
* @return
*/
@Select("select id,name,age,gender from admin where name = #{name}")
List<Admin> getAdminsByParams(Map<String,Object> name);
}
核心配置文件中加入(mybatis-config.xml)
<mappers>
<mapper class="com.xinzhi.dao.AdminMapper"/>
</mappers>
进行测试:
/**
* @author 空巷
* @Date 2020/5/28
*/
public class TestAdmin {
private SqlSession session;
@Before
public void before() {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
}
//通过id查询用户
@org.junit.Test
public void selectAdminById() {
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
Admin Admin = AdminMapper.selectAdminById(1);
System.out.println(Admin);
}
//添加用户
@org.junit.Test
public void addAdmin() {
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
Admin Admin = new Admin(6,"aaa",20,"男");
int i = AdminMapper.addAdmin(Admin);
System.out.println(i);
}
//通过id修改用户
@org.junit.Test
public void updateAdminById(){
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
Admin Admin = new Admin(7,"zxcas",20,"男");
int i = AdminMapper.updateAdmin(Admin);
System.out.println(i);
}
//删除用户
@org.junit.Test
public void deleteAdmin(){
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
int i = AdminMapper.deleteAdmin(8);
System.out.println(i);
}
//模糊查询
@org.junit.Test
public void getAdminByName(){
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
List<Admin> AdminByName = AdminMapper.getAdminByName("%张%");
System.out.println(AdminByName);
}
//使用map
@org.junit.Test
public void getAdminsByParams(){
AdminMapper AdminMapper = session.getMapper(AdminMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("name","张三");
List<Admin> AdminsByParams = AdminMapper.getAdminsByParams(map);
System.out.println(AdminsByParams);
}
@After
public void after() {
//统一提交事务
session.commit();
session.close();
}
}
整体看起来是不是清爽了很多? 但是个人不建议使用,因为它不方便维护。
动态sql
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if(最常用)
if相当于java中的if语句(判断语句)举个例子:
<select id="selectUserById" resultType="User">
select id,name,age,gender from user where 1=1
<if test="id != null and id != ''">
AND id = #{id}
</if>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null and age != ''">
AND age = #{age}
</if>
<if test="gender != null and gender != ''">
AND gender = #{gender}
</if>
</select>
choose、when、otherwise元素(任选其一)
有些时候我们还需要多种条件的选择,在Java中我们可以使用switch、case、default语句,而在映射器的动态语句
中可以使用choose、when、otherwise元素。
<select id="getUserByName" resultType="User">
select id,name,age,gender from user where 1=1
<choose>
<when test="name != null and name != ''">
AND name like #{name}
</when>
<when test="id != null">
AND id = #{id}
</when>
</choose>
</select>
where元素
上面的select语句我们加了一个 1=1 的绝对true的语句,目的是为了防止语句错误,变成 SELECT * FROM
student WHERE 这样where后没有内容的错误语句。这样会有点奇怪,此时可以使用 元素。
<select id="selectUserById" resultType="User">
select id,name,age,gender from user
<where>
<if test="id != null and id != ''">
AND id = #{id}
</if>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null and age != ''">
AND age = #{age}
</if>
<if test="gender != null and gender != ''">
AND gender = #{gender}
</if>
</where>
</select>
trim元素
有时候我们要去掉一些特殊的SQL语法,比如常见的and、or,此时可以使用trim元素。trim元素意味着我们需要
去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉的那种字符串,
suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串。
<select id="select" resultType="com.xinzhi.entity.User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND">
<if test="username != null and username != ''">
AND username LIKE concat('%', #{username}, '%')
</if>
<if test="id != null">
AND id = #{id}
</if>
</trim>
</select>
set元素
在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。注意:set元
素遇到,会自动把,去掉。
<update id="updateUser" parameterType="User">
UPDATE `user`
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender != null">
gender = #{gender}
</if>
</set>
WHERE id = #{id}
</update>
foreach元素
foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口。
<select id="select" resultType="com.xinzhi.entity.User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</select>
- collection配置的是传递进来的参数名称
- item配置的是循环中当前的元素。
- index配置的是当前元素在集合的位置下标。
- open和 close配置的是以什么符号将这些集合元素包装起来。
- separator是各个元素的间隔符。
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然
后使用时直接调用。
<sql id="update-user">
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender != null">
gender = #{gender}
</if>
</set>
</sql>
<!--引用sql片段 refid 指定sql 可引用其他的sql-->
<update id="updateUser" parameterType="User">
UPDATE `user`
<include refid="update-user"/>
WHERE id = #{id}
</update>
数据库关系的处理
有时候我们设计表的时候,需要添加外键,一对多,多对一等关系。
先来设计个数据库。
CREATE TABLE `grade` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`class_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`stu_num` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`s_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `g_id` (`s_id`),
CONSTRAINT `g_id` FOREIGN KEY (`s_id`) REFERENCES `grade` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
针对这种关系型表,有如下三种处理办法
自己手动维护
不就是多写几个sql语句吗?通过逻辑,进行手动拼装。
在多的一方维护关系
实体类:(在这里我已班级和学生为例 一个班级有多个学生)
/**
* @author 空巷
* @Date 2020/5/29
* 班级表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Grade implements Serializable {
private int id;
//班级名称
private String className;
}
/**
* @author 空巷
* @Date 2020/5/29
* 学生表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
private int id;
private String stuNum;
private String name;
private Grade grade;
}
对应的Mapper接口类
/**
* @author 空巷
* @Date 2020/5/29
*/
public interface GradeMapper {
/**
* 通过id获取班级
* @param id
* @return
*/
Grade getGradeById(int id);
}
/**
* @author 空巷
* @Date 2020/5/29
*/
public interface StudentMapper {
/**
* 获取所有的班级
* @return
*/
List<Student> getAllStudent();
}
编写对应的mapper.xml配置文件
<?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="com.xinzhi.dao.StudentMapper">
<resultMap id="StudentGrade" type="com.xinzhi.entity.Student">
<!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
<association property="grade" column="s_id" javaType="com.xinzhi.entity.Student" select="getGrade"/>
</resultMap>
<select id="getAllStudent" resultMap="StudentGrade">
SELECT * FROM student
</select>
<select id="getGrade" resultType="Grade">
SELECT * FROM grade WHERE id = #{id}
</select>
</mapper>
测试:
/**
* @author 空巷
* @Date 2020/5/29
*/
public class TestStudent {
private SqlSession session;
@Before
public void before(){
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
}
@Test
public void testGetAllStudent(){
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
List<Student> allStudent = studentMapper.getAllStudent();
System.out.println(allStudent);
}
@After
public void after(){
session.commit();
session.close();
}
}
切记:得再核心配置文件中加入mapper
<mapper resource="mapper/GradeMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
即可查询结果。
在一的一方维护关系
实体类:
/**
* @author 空巷
* @Date 2020/5/29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Grade implements Serializable {
private int id;
private String className;
List<Student> students;
}
/**
* @author 空巷
* @Date 2020/5/29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
private int id;
private String stuNum;
private String name;
}
对应的mapper接口类
/**
* @author 空巷
* @Date 2020/5/29
*/
public interface GradeMapper {
/**
* 通过id获取班级
* @param id
* @return
*/
Grade getGradeById(int id);
}
/**
* @author 空巷
* @Date 2020/5/29
*/
public interface StudentMapper {
/**
* 获取所有的班级
* @return
*/
List<Student> getAllStudent();
}
mapper.xml配置文件
<?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="com.xinzhi.dao.GradeMapper">
<!-- <cache eviction="LRU" flushInterval="10000" size="1024" readOnly="true"/>-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache" eviction="LRU"
flushInterval="10000" size="1024" readOnly="true"/>
<select id="getGradeById" resultMap="grade">
SELECT s.id sid,s.stu_num stuNum,s.`name` sName,g.class_name,g.id gid,g.class_name className
from grade g,student s WHERE s.s_id = g.id AND g.id = #{id}
</select>
<resultMap id="grade" type="Grade">
<!-- id为主键 -->
<id property="id" column="gid"/>
<result property="className" column="className"/>
<collection property="students" ofType="Student">
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result property="id" column="sid"/>
<result property="stuNum" column="stuNum"/>
<result property="name" column="sName"/>
</collection>
</resultMap>
</mapper>
测试:
/**
* @author 空巷
* @Date 2020/5/29
*/
public class TestGrade {
private SqlSession session;
SqlSessionFactory sqlSessionFactory;
@Before
public void before(){
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
}
@Test
public void testGetStudentById(){
GradeMapper mapper = session.getMapper(GradeMapper.class);
Grade gradeById = mapper.getGradeById(1);
System.out.println(gradeById);
}
@After
public void after(){
session.commit();
session.close();
}
}
日志配置
在执行了上述一些列操作后,你会发现 sql语句看不到 报错了不知道。因此我们需要配置一个日志,把sql语句输出出来,查看一下中间的过程。
标准日志实现
指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
STD:standard out:输出
STDOUT_LOGGING:标准输出日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
比较牛逼点的 log4j
- 需要先引入 log4j 的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 配置文件编写 log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
去核心配置文件中加入 setting
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
测试 看控制台
MyBatis 缓存
缓存会单独出一篇博文。