目录
3.2 mapper代理方法(只需要mapper接口-->相当于dao接口)
0.框架架构
SqlSessionFactoryBuilder(只需当成一个工具类,只用new一次,不用单例管理)
------->>>SqlSessionFactory(单例)
------->>>SqlSession(线程不安全)
1.为什么要使用mybatis(jdbc的缺陷)
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
- Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
- 使用preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
2.和hibernate的区别
- mybatis不是一个完全的ORM框架,因为可以自己决定输出/输入映射,需要自己对SQL语句进行修改和优化
>>>>适用需求变化较多的项目:互联网项目
- hibernate是标准的ORM框架,不需要写SQL,对SQL的优化和修改困难
>>>>适用需求变化不多的项目
3.开发dao两种方法
-
3.1 原始dao开发方法
需要写dao接口和dao实现类和Statement的Xml。
需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession
(通过SqlSession来调用xml中配置好的statement了)
*存在问题:
- dao实现类有大量重复代码
- statement的id的硬编码
- 调用SqlSession方法传入的变量,由于适用泛型,即使变量类型错误,在编译阶段也不报错
-
3.2 mapper代理方法(只需要mapper接口-->相当于dao接口)
需要写mapper.java(相当于接口)和mapper.xml
编写mapper.java需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象
开发规范:
- 在mapper.xml中namespace等于mapper.java类地址
- mapper.java的方法名和mapper.xml中statement的id一致
- mapper.java方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致
- mapper.java方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
得到SqlSession后,再通过SqlSession.getMapper(...mapper.class)来获得mapper代理对象来调用xml中配置好的statement
4.#{}和${}
编写Statement的XML时用于表示输入变量的占位符(eg:SELECT * FROM ...WHERE username=#{username})
4.1 #{} 和 ${} 里面填的内容
如果输入的参数是简单类型,#{}中的参数名可以任意,可以使value或者其他什么
${}在传入简单类型时只能使用value
4.2 #{} 和 ${} 在预编译中的处理是不一样的
- #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,变成如下的 sql 语句:
username = 'username'
解析之后才会变成username = 'zhangsan'
- $将传入的数据的值直接显示生成在sql中,变成如下
username = 'zhangsan'
解析后还是一样username = 'zhangsan'
所以优先使用 #{}。因为 ${} 会导致 sql 注入的问题,${}用于拼接sql
5.SqlMapConfig.xml
5.1 配置内容
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
5.2 别名的设置
<typeAliases>
<typeAlias type="...类路径..." alias="...所需变换的别名..."/>
</typeAliases>
5.3 加载mapper映射文件
<mappers>
<mapper resource="...相对路径..."/>
</mappers>
*下面两种都要遵循规范,需要将mapper的接口类和mapper.xml映射文件名保持一致,且在同一个目录中
<mappers>
<!-- 单个导入 -->
<mapper class="...包路径...">
<!-- 批量导入 -->
<package name="...包路径..."/>
</mappers>
5.4读取属性的顺序
<properties resource="db.properties" url="">
<property name="" value=""/>
</properties>
- 在property元素体内定义的属性首先被读取
- 然后读取properties元素中resource或url加载的属性,会覆盖已读的属性值
- 最后读取parameterType传递的属性,会覆盖已读取的同名属性
parameterType是输入映射要传入的参数,传入的参数值也会覆盖原有的相同名字的值
6.映射
-
6.1输入映射
传入pojo的包装对象
传入简单类型
传入hashmap
-
6.2输出映射
*resultType:
使用resultType进行输出映射,查询出来的列名要和pojo中的属性名一致(有可能要自己创一个pojo类),才能映射成功
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象。
*resultMap:
而使用resultMap,就可以解决列名和属性名不一致导致不能映射成功的问题
使用:现在Mapper.xml定义resultMap标签
eg: *将查询出来的列名id__映射到user对象的属性id,将查询出来的列名username__映射到user对象的属性username
<resultMap type="com.bean.user" id="resultMap">
<id column="id__" property="id"/>
<result column="username__" property="username"/>
</resultMap>
>>>type:resultMap要映射的java对象类型
>>>id-column:得到的列名
>>>id-property:要对应的javabean的属性名
*resultType和resultMap区别:
①resultType输出的字段名要和输出映射的pojo类的属性名一致
②在一对多的时候,使用resultType会产生重复字段问题
eg:查出来一个订单对应N个订单明细信息,就会显示成N条记录,但我们需要只是一条订单记录,记录里面有N条订单明细信息
而如果要解决,就要使用双重循环(第一层循环订单信息,第二层循环对应订单的订单明细信息)
如果用resultMap就在配置resultMap的时候
通过<collection></collection>标签来自动去重,原理也是双重for循环,只不过系统帮我们解决了
所以在一对多以及多对多特殊情况时
还是使用resultMap
单表查询的时候就使用resultType
7.动态sql
差不多同jstl一样
eg:
<select ...>
SELECT * FROM USER
<!-- 用where标签可以自动省掉去掉条件中第一个and,或者直接用1=1来拼接也可以 -->
<where>
<if test="userNew!=null">
<if test="userNew.username!=null and userNew.username!=''">
and user.username = #{userNew.username}
</if>
<if test="userNew.password!=null and userNew.password!=''">
and user.password= #{userNew.password}
</if>
</if>
</where>
</select>
对于可重用的sql,可用sql片段来重用
>>定义:<sql id="hello">...sql...</sql>
>>使用:<include refid="hello"></include>
需要循环加sql的可以用foreach标签
<foreach collection="" item="" open="" close="" separator="">..sql...</foreach>
>>collection:传入对象中的集合属性
>>item:遍历生成的每一个对象
>>open:以什么sql作为开始拼接的信号
>>close:以什么sql作为结束拼接的信号
>>separator:每加入一个sql之间的分隔字符串
8.延迟加载
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度快
要在SqlMapConfig
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="ture"/>
<!-- 将积极加载改为消极加载==按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
只能使用resultMap来实现延迟加载,
配置resultMap时使用标签<association property="" javatype="" select="" column=""></association>
>>>property:将查到的对象映射到哪个类属性
>>>javatype:需要延迟加载的表的类路径
>>>select:指定延迟加载需要执行的statement的id
>>>column:两张表之间关联的那个键名
<association></association>是用来配置输出映射中类的属性为另一个类(比如User类里面有Customer类这个属性)
如果不需要延迟加载,可以不用后面两个属性(select / column)
9.查询缓存
-
一级缓存
用同一个sqlSession执行同一个mapper中的select方法时,可以从缓存中提取数据
清空缓存的情况:1.sqlSession.close() 2.sqlSession.commit()
-
二级缓存
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中
开启二级缓存
SqlMapConfig:
<settings>
...
<setting name="cacheEnabled" value="true"/>
...
</settings>
需要开启二级缓存的Mapper.xml(type属性是用来整合其他缓存框架[ehcache-->集群缓存],写实现cache实现类的类路径)
<mapper namespace="...">
...
<cache type=""/>
...
</mapper
useCache="false" --> 用在select标签中 可以禁用二级缓存
flushCache="false" --> 用在insert标签中可以导致脏读,即改变数据库数据后也不刷新缓存
局限性:
简单来说,就是已知二级缓存里面已经存了1K个数据,但是只要其中一个SqlSession提交了数据(commit)这1K个数据全部都清空了,就导致缓存的命中率很低
官方来说,mybatis二级缓存对细粒度的数据级别的缓存实现不好,解决此类问题需要在业务层根据需求对数据有针对性缓存
10.MyBatis整合其他框架
MyBatis提供了一个Cache接口,如果要实现自己的缓存逻辑,只需要实现该接口即可
用Mybatis和ehcache整合,在Mybatis和ehcache的整合包中提供了一个cache接口的实现类
Mybatis默认的cache的实现类:
只需在mapper.xml中配置cache标签的type属性,以及ehcache的配置文件即可完成配置
11.Mybatis和Spring的整合
导包
配置SqlSessionFactory(SqlMapConfig和dataSource)
和dataSource(driverCalssName/url/username/password/...)
两种dao的开发方法:
11.1原始dao开发:
1.User.xml 写Statement
2.写dao
3.写dao的实现类(调用selectOne或selectList(namespace.select.id))
*让dao实现类继承SqlSessionDaoSupport(已有属性SqlSessionFactory)
4.在Spring的配置文件中配置dao实现类的SqlSessionFactory
11.2mapper代理开发:
~1.mapper.xml和mapper.java(就是dao)
~2.0.通过mapperFactoryBean来创建对象(一个mapper需要一个FactoryBean来创建对象)
~2.1.通过MapperScannerConfigurer进行mapper扫描(mapper的批量扫描)
两个都需要注入sqlSessionFactory(第二种方法要注入sqlSessionFactoryBeanName,先后执行问题)
12.逆向工程
12.1 什么是逆向工程
由数据库的表生成java代码,以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml、pojo等)
12.2 使用
生成代码配置文件
<?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>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="mysql">
</jdbcConnection>
<!-- <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:生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.itcast.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itcast.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
</context>
</generatorConfiguration>
执行生成程序