什么 mybatis
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架;
Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。Mybatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
优点
1、简单易学
mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现
2、灵活
mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
3、解除sql与程序代码的耦合
通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
4、提供映射标签,支持对象与数据库的orm字段关系映射
5、提供对象关系映射标签,支持对象关系组建维护
6、提供xml标签,支持编写动态sql。
缺点
1、编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
2、SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
3、框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
4、二级缓存机制不佳
Mybatis框架认知
mybatis所需要的映射文件包含三部分:
SQL
映射规则
POJO
mybatis《—- pojo—->注解 SQL_Mapper —->数据
应用程序—->对象《—- 接口 xml映射文件 《—-库
mybatis的基本构成
SqlSessionFactoryBuilder(构造器):他会根据配置信息或者代码来生成SqlSessionFactory(工厂接口).
.SqlSessionFactory:依靠工厂来生成SqlSession(会话).
.SqlSession:是一个既可以发送SQL去执行并返回结果,也可以获取Mapper接口.
.Sql Mapper:他是MyBatis新设计的组件,他是由一个java接口和xml文件(或者注解)构成的.需要给出对应的SQL和映射规则,
他负责发送SQL去执行,并返回执行结果.
简单的mybatis使用
1.导入jar(maven)
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--代码简化器-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
主配置文件
<?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"
>
<!--mybatis的主要配置文件-->
<configuration>
<properties resource="database.properties"/>
<settings>
<!-- resultMap自动映射级别(在主配置文件中进行配置)
1.NONE禁止自动匹配
2.PARTIAL默认自动匹配,不包括内部嵌套
3.FULL自动匹配所有
-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!--开启事务日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true" />
<!-- 开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"></setting>
<!-- 关闭立即加载-->
<setting name="aggressiveLazyLoading" value="false"/>
<!--自动映射-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
<typeAliases>
<package name="com.jbit.pojo"/>
</typeAliases>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源-->
<dataSource type="POOLED">
<!--连接数据库的四个基本信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper class="com.jbit.dao.UserDao" />
</mappers>
</configuration>
sql映射文件
<?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="全限定类名">
</mapper>
静态资源过滤(pom)
<!--获取java xml 配置文件 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
日志文件
log4j.rootLogger=DEBUG,CONSOLE,file
#log4j.rootLogger=ERROR,ROLLING_FILE
log4j.logger.cn.smbms.dao=debug
log4j.logger.com.ibatis=debug
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug
log4j.logger.java.sql.Connection=debug
log4j.logger.java.sql.Statement=debug
log4j.logger.java.sql.PreparedStatement=debug
log4j.logger.java.sql.ResultSet=debug
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
######################################################################################
# Console Appender \u65e5\u5fd7\u5728\u63a7\u5236\u8f93\u51fa\u914d\u7f6e
######################################################################################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
######################################################################################
# DailyRolling File \u6bcf\u5929\u4ea7\u751f\u4e00\u4e2a\u65e5\u5fd7\u6587\u4ef6\uff0c\u6587\u4ef6\u540d\u683c\u5f0f:log2009-09-11
######################################################################################
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n
log4j.logger.com.opensymphony.xwork2=error
数据库配置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/smbms
useUnicode=true&characterEncoding=utf-8
username=root
password=1234
动态sql
<?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.jbit.dao.EasybuyNewsDao">
<select id="fillA" resultType="EasybuyNews" parameterType="map">
select * from easybuy_news
/*where 至少有一个子元素的情况下使用 会根据条件智能的去除 and 或or */
<where>
<choose>
<when test="title != null">
id = #{title}
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</where>
</select>
<update id="Update" parameterType="map">
update easybuy_news
<set >
/* include 根据 refid 来获取sql片段 id = if的sql片段 */
<include refid="if"/>
</set>
where id = #{id};
</update>
<sql id="if">
/* if 根据 test 表达式的返回结果 判断是否执行包含的条件 */
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content}
</if>
</sql>
<select id="fillAll" parameterType="map" resultType="EasybuyNews">
select * from easybuy_news
<where>
<foreach collection="ids" index="index" item="id" open="id in(" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
<!-- 一次遍历多个条件 collection(被遍历的) item(每次遍历得到的) open -->
<foreach collection="ids" index="index" item="id" open="id in(" close=")" separator=",">
</mapper>
resultMap 映射
<!--自动映射
PARTIAL 非嵌套 (默认开启)
NONE 不自动映射
FULL 所有 resultMap 都映射
-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
基本resultMap
<select id="allUser" resultMap="userall">
select * from smbms.smbms_user;
</select>
<!-- java对象属性 和表字段的 映射 -->
<resultMap id="userall" type="smbmsUser">
<id property="id" column="id"/>
</resultMap>
第一个id:代表通过这个id值可以找到这个map
第二个id:代表主键
result:代表非主键
property:代表java中的属性名
column:代表数据库的字段名
javaType:java属性的类型
jdbcType:数据库字段的类型 并且只能是大写
一对一 resultMap
<select id="userRoleAddressAll" resultMap="All">
SELECT u.id ,u.userName,r.roleName FROM smbms_user u , smbms_role r
WHERE u.userRole=r.id
</select>
<!-- 复杂类型的查询-->
<resultMap id="All" type="userRoleAddress">
<!-- association 一对一 复杂类型的 字段映射 property对应的属性名 javaType 属性类型-->
<association property="smbmsUser" javaType="smbmsUser">
<!-- id 效率更高 一般主键使用 property 对应属性名column对应的表列名-->
<id property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="userRole" column="userRole"/>
</association>
<association property="smbmsRole" javaType="smbmsRole">
<id property="id" column="id"/>
<result property="roleName" column="roleName"/>
</association>
</resultMap>
一对多resultMap
<select id="userAddressAll" resultMap="userAddressAll" parameterType="int">
SELECT u.id,u.userName,a.addressDesc FROM
smbms_user AS u,
smbms_address AS a
WHERE u.id = a.userId
<if test="id != null">
and u.id = #{id}
</if>
</select>
<!--一对多-->
<resultMap id="userAddressAll" type="smbmsUser">
<id property="id" column="id"/>
<!-- collection(集合) ofType(集合的泛型) -->
<!--嵌入 一个resultMap -->
<collection property="smbmsAddress" ofType="smbmsAddress" resultMap="addressMap">
</collection>
</resultMap>
<!--复用型 resultMap-->
<resultMap id="addressMap" type="smbmsAddress">
<id property="id" column="id"/>
<result property="addressDesc" column="addressDesc"/>
</resultMap>
逆向工程
- 导入依赖
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
2.自动生成代码插件
<build>
<plugins>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<!-- 在src/main/resources 目录下创建generator文件夹并在其中建立名为
generatorConfig.xml的配置文件,作为mybatis-generator-maven-plugin 插件的执行目标-->
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
3.编写配置文件
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--导入属性配置-->
<properties resource="database.properties"/>
<!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包-->
<classPathEntry location="${driverLocation}"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- Mysql数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="${driver}"
connectionURL="${url}"
userId="${username}"
password="${password}">
</jdbcConnection>
<!-- Oracle数据库
<jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection>
-->
<!-- 默认为false,把JDBC DECIMAL 和NUMERIC类型解析为Integer,为true时
把JDBC DECIMAL 和NUMERIC类型解析为java.math.BigDecimal -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成POJO类的位置 -->
<javaModelGenerator targetPackage="com.sl.demo.pojo" targetProject=".\src\main\java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.sl.demo.mapper" targetProject=".\src\main\resources">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetProject:mapper接口生成的的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.sl.demo.mapper" targetProject=".\src\main\java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据表 -->
<table tableName="student" domainObjectName="Student"></table>
<table tableName="grend" domainObjectName="Grend"></table>
<!-- 有些表的字段需要指定java类型
<table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
<property name="useActualColumnNames" value="true"/>
<generatedKey column="ID" sqlStatement="DB2" identity="true" />
<columnOverride column="DATE_FIELD" property="startDate" />
<ignoreColumn column="FRED" />
<columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
</table> -->
</context>
</generatorConfiguration>
分页
1.导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2<ersion>
</dependency>
//1.使用Mapper接口方式的调用,推荐这种使用
Page<Object> objects = PageHelper.startPage(2, 3);
//2.Mapper接口方式的调用,推荐这种使用。
PageHelper.offsetPage(1, 10);
//3. Lambda
Page<SmbmsProvider> page =
PageHelper.startPage(2,3).doSelectPage(() -> mapper.allSmbmsProvider());
缓存机制
一级缓存 默认开启 :applaction
Mybatis自带二级缓存: [ 同一个namespace ]生成的mapper对象
回顾: namespace的值 就是接口的全类名(包名.类名),通过接口可以产生代理对象(studentMapper对象)
-->namespace决定了studentMapper对象的产生
结论:只要产生的xxxMapper对象来自于同-一个namespace,则这些对象共享二级缓存。
注意:二级缓存的范围是同一个namespace,如果有多个xxMapper. xml的namespace值相同,则通过这些
禁用: select标签中useCache=" false"
清理:与清理一级缓存的方法相同,
1.commit(); (一般执行增删改时 会清理掉缓存;设计的原因是为了防止脏数据)
在二级缓存中,commit()不能是查询自身的commit。
commit会清理一级和二 级缓存;但是清理二级缓存时,不能是查询自身的commit;
2.在select标签中增加属性|flushCache= "true" 清理此sql的缓存
第三方 或者自定义 2级缓存 必须实现 org.apache.ibatis.cache 接口
延迟加载
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭立即加载-->
<setting name="aggressiveLazyLoading" value=" false"/>
</settings>
//通过 select 引入 其他mapper 的sql 开启懒加载
//当需要时才 执行 sql
<association property="grend" javaType="grend" select="com.sl.demo.mapper.GrendMapper.allGrend" column="grendId">
<!-- <id property="id" column="id"/>
<result property="name" column="name"/>-->
</association>
自定义mybatis类型处理器
配置转换器(主配置文件中)
handler:代表映射转换类型的类(自定义配置帮助类) javaType:java的类型 jdbcType:数据库的类型
<typeHandlers>
<typeHandler handler="org.hbz.converter.BooleanAndInt" javaType="boolean" jdbcType="Integer"></typeHandler>
</typeHandlers>
java-数据库(jdbc类型)
实例:
实体类Student boolean stuSex true:男 false :女
数据库 student int stuSex 1:男 0::女
自定义类型转换器(boolean-int) 步骤:
(1) 创建转换器
需要实现TypeHandler接口
通过阅读源码发现BaseTypeHandler是实现类 因此实现类型转换器有两种选择
i:实现TypeHandler
ii:继承BaseTypeHandler
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class BooleanAndInt extends BaseTypeHandler<Boolean> {
//set:java-->数据库
/**
*
* @param preparedStatement PreparedStatement对象
* @param i PreparedStatement对象操作参数的位置
* @param o java值
* @param jdbcType jdbc操作数据库的值
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Boolean o, JdbcType jdbcType) throws SQLException {
if (o){//true编程1
preparedStatement.setInt(i,1);
}else{//false就是0
preparedStatement.setInt(i,0);
}
}
//get:数据库-->java
/**
*
* @param resultSet 数据集
* @param s 数据库列名
* @return 返回转换后的值
* @throws SQLException
*/
@Override
public Boolean getNullableResult(ResultSet resultSet, String s) throws SQLException {
int bool=resultSet.getInt(s);
return (bool==1?true:false);
}
//get:数据库-->java
/**
*
* @param resultSet 数据集
* @param i 下标
* @return 转换后的数据
* @throws SQLException
*/
@Override
public Boolean getNullableResult(ResultSet resultSet, int i) throws SQLException {
int bool=resultSet.getInt(i);
return (bool==1?true:false);
}
//get:数据库-->java
/**
*
* @param callableStatement 存储过程对象
* @param i 下标
* @return 转换后的值
* @throws SQLException
*/
@Override
public Boolean getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
int bool=callableStatement.getInt(i);
return (bool==1?true:false);
}
}
返回值 resultMap和resultType的区别
情况1:如果类中的属性类型和表中的字段类型能够合理的被识别(String-varchar) ,则使用resultType,否则使用resultMap
情况2:如果类中的属名称和表中的字段名称能够合理的被识别(stuno-stuno) ,则使用resultType,否则使用resultMap
两种取值符号
${} 和 #{}的区别
(1)#{任意值}
${value},其中的标识符只能是value
(2)#{}自动给String类加上‘ ’ (自动类型转换)
${}原样输出,但是适用于 动态排序(动态字段)
如果使用#{}会自动的添加‘ ‘号 就不符合数据库的语句
(3) #{}可以防止sql注入
${}不防止
动态排序的实例:
<!-- 动态排序-->
<select id="selectStudentByOrderBy" parameterType="string" resultType="student">
select * from student order by ${value} desc
</select>
注解开发
推荐使用xml开发(而注解开发适用于一些小项目)
1.配置主文件的mapper标签 通过class来映射接口
<mapper class="org.sl.dao.IStudentDao"></mapper>
无论是注解还是xml都可以可以一劳永逸的方式直接填写包名存在哪里 (批量引入)
<package name="org.sl.dao"/>
2.将sql语句写在接口的方法的上面
@Select("select * from student where stuNo=#{stuNo}")
Student queryStudentByNo(int stuNo);