对原生态jdbc程序中问题总结
- 数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能。
设想:使用数据库连接池管理数据库连接。 - 将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。 - 向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。
设想:将sql语句及占位符号和参数全部配置在xml中。 - 从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,,不利于系统维护。
设想:将查询的结果集,自动映射成java对象。
mybatis框架
mybatis是什么?
mybatis是一个持久层的框架,是apache下的顶级项目。
mybatis托管到goolecode下,再后来托管到github下。
mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。
mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)
mybatis框架
- 获取会话工厂
- 获取会话对象
- 会话操作
- 会话提交和关闭
Hello World
需求
根据用户id(主键)查询用户信息
根据用户名称模糊查询用户信息
添加用户
删除用户
更新用户
依赖lib
- mybatis(github下载)
- jdbc数据库驱动
下载与平台无关的Platform Independent,下载zip格式(注意版本问题,点Looking for previous GA versions?是以前版本)
创建文件
- 在src同级新建一个名为config的Source Folder
- 在config下创建一个名为sqlmap的包,用来存放对应关系
- 创建一个SqlMapConfig.xml文件配置mybatis的运行环境,数据源,事务等
数据库
1 | CREATE TABLE `user` ( |
bean类
1 | public class User { |
SqlMapConfig.xml
配置mybatis的运行环境,数据源、事务等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<configuration>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理,事务控制由mybatis-->
<transactionManager type="JDBC" />
<!-- 数据库连接池 由mybatis控制-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="6" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource = "sqlmap/User.xml"/>
</mappers>
</configuration>
- 注意如果和spring整合后就不用这样配置了
根据用户id(主键)查询用户信息
映射文件
config/sqlmap/User.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- namespace是命名空间,作用就是对sql进行分类化管理,理解为sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用
-->
<mapper namespace="test">
<!-- 在映射文件配置很多sql语句 -->
<!-- 通过id查询用户表记录 -->
<!-- 通过select执行数据库查询
id:标识映射文件中的sql
将sql语句封装到mappedStatement对象中,所以将id称为statement的id
parameterType:指定输入类型参数,这里指定int型
#{}表示一个占位符号
#{id}:其中id表示输入类型的参数,参数名称就是id,如果输入参数是基本类型,#{}中的名可以任意,可以用value或者其他
resultType:指定输出结果所映射的类型,select指定resultType表示将单条记录映射成Java对象
-->
<select id="findUserById" parameterType="int" resultType="cn.xwmdream.Main.User">
select * from user where id=#{hhh}
</select>
</mapper>
- 在映射文件中写入sql语句,并且声明了输入类型和输出类型
- 使用#{}表示占位符,比如传入了1,那么实际sql为select * from user where id=’1’;
代码
1 | //mybatis配置文件 |
根据用户名称模糊查询用户名
映射文件
1 | <!-- 根据用户名称模糊查询用户信息,可能返回多条 |
- 使用了like进行模糊匹配,这是传入的参数需要用%等一些占位符
运行
1 | //mybatis配置文件 |
- 使用了selectList返回List
- 因为是模糊查询,所以需要%
另一种方法
上面的话每一个参数都要加%,试想可不可以把%写到sql语句中
修改映射文件
1
2
3<select id="findUserByName" parameterType="java.lang.String" resultType="cn.xwmdream.Main.User">
select * from user where username like '%${value}%'
</select>在映射文件中用${}代替#{},表示拼接的意思,就是把传入的值不加任何修饰拼接到sql串中
- 如果传入的是基本类型,那么${}中只能使用value
- 但是这样会引起sql注入,所以不建议使用
添加用户
映射文件
1 | <!-- 添加用户 |
运行
1 | //mybatis配置文件 |
查看提交记录的主键
对于mysql自增主键,执行提交记录后会生成一个自增主键
如果查看自增主键的值,需要在刚刚插入记录之后执行1
select LAST_INSERT_ID();
- 修改映射文件,使insert之后执行上述命令获取插入的id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<!-- 添加用户
parameterType:指定输入 参数类型是pojo(包括 用户信息)
#{}中指定pojo的属性名,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值
-->
<insert id="insertUser" parameterType="cn.xwmdream.Main.User">
<!--
将插入数据的主键返回,返回到user对象中
SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用与自增主键
keyProperty:将查询到主键值设置到parameterType指定的对象的哪个属性
order:SELECT LAST_INSERT_ID()执行顺序,相对于insert语句来说它的执行顺序
resultType:指定SELECT LAST_INSERT_ID()的结果类型
-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})
<!--
使用mysql的uuid()生成主键
执行过程:
首先通过uuid()得到主键,将主键设置到user对象的id属性中
其次在insert执行时,从user对象中取出id属性值
-->
<!-- <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
SELECT uuid()
</selectKey>
insert into user(id,username,birthday,sex,address) value(#{id},#{username},#{birthday},#{sex},#{address}) -->
</insert>
1 | ... |
- 在insert中增加了一个selectKey指定他的order为AFTER表示在sql执行之后执行那个命令,并且把int类型的返回值储存到id属性中
- 在java中因为已经把插入的递增主键存入到id中,所以可以通过getId得到插入的id
- 上述代码中还有一个在insert之前生成一个字符串类型的uuid,并且插入到数据库中
删除和更新用户
映射文件
1 | <!-- 删除 用户 |
运行
1 | // mybatis配置文件 |
- 入门程序的查询不需要commit,其他都需要commit提交事务
mybatis开发dao的方法
SqlSession使用范围
SqlSessionFactoryBuilder
通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory
将SqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。
在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。
SqlSessionFactory
通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。
将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory。
SqlSession
SqlSession是一个面向用户(程序员)的接口。
SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)、。
SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。
SqlSession最佳应用场合在方法体内,定义成局部变量使用。
原始dao开发方法(程序员需要写dao接口和dao实现类)
思路
程序员需要写dao接口和dao实现类。
需要向dao实现类中注入SqlSessionFactory(构造方法注入),在方法体内通过SqlSessionFactory创建SqlSession
接口和实现类
1 | public interface UserDao { |
程序调用
1 | // mybatis配置文件 |
- 会显示id为26的User对象
总结原始 dao开发问题
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
- 调用sqlsession方法时将statement的id硬编码了
- 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。
mapper代理
程序员还需要编写mapper.xml映射文件
程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象。
编写mapper映射文件
UserMapper.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- namespace是命名空间,作用就是对sql进行分类化管理,理解为sql隔离
注意:使用mapper代理方法开发,namespace必须等于Mapper接口的全限定名
-->
<mapper namespace="cn.xwmdream.Main.UserMapper">
<select id="findUserById" parameterType="int" resultType="cn.xwmdream.Main.User">
select * from user where id=#{hhh};
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.xwmdream.Main.User">
select * from user where username like #{hhh}
</select>
</mapper>
- 这个namespace必须等于Mapper接口的全限定名,statement的id必须等于接口的方法名
Mapper接口
1 | public interface UserMapper { |
- 方法名必须和映射文件中的statement的id相同
- 方法的输入参数和返回值必须和映射文件的paramenterType和resultType的类型相同
在SqlMapConfig.xml中加载mapper.xml
1 |
|
运行
1 | // mybatis配置文件 |
mapper开发规范
- 在mapper.xml中namespace等于mapper接口地址
- mapper.java接口中的方法名和mapper.xml中statement的id一致
- mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。
- mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。
- mapper会根据标签是select或者insert调用不同的方法
- 如果mapper方法返回单个对象(非集合对象),代理对象内部通过selectOne查询数据库。
SqlMapConfig.xml
mybatis的全局配置文件SqlMapConfig.xml,配置内容如下:
- properties(属性)
- settings(全局配置参数)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理)
- dataSource(数据源)
- environment(环境子属性对象)
- mappers(映射器)
properties属性
需求:
将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。
在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。
db.properties1
2
3jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=xxx
在sqlMapConfig.xml加载属性文件:1
2
3
4
5
6
7
8
9
10
11
12
13
<configuration>
<!-- 加载属性文件 -->
<properties resource="db.properties">
<!--properties中还可以配置一些属性名和属性值 -->
<!-- <property name="jdbc.driver" value=""/> -->
</properties>
...
</configuration>
注意: MyBatis 将按照下面的顺序来加载属性:
- 在 properties 元素体内定义的属性首先被读取。
- 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
- 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。所以建议parameterType中使用${}
建议:
- 不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
- 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX
settings全局参数配置
mybatis框架在运行时可以调整一些运行参数。
比如:开启二级缓存、开启延迟加载。。
全局参数将会影响mybatis的运行行为。
typeAliases(别名)重点
需求
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。
如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
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 |
自定义别名
单个别名定义
1 | <!-- 针对单个别名定义 |
引用别名:1
2
3<select id="findUserById" parameterType="int" resultType="user">
select * from user where id=#{hhh};
</select>
批量定义别名(常用)
1 | <!-- 批量别名定义 |
引用别名
1
2
3<select id="findUserById" parameterType="int" resultType="user">
select * from user where id=#{hhh};
</select>首字母大写小写都可以
typeHandlers(类型处理器)
mybatis中通过typeHandlers完成jdbc类型和java类型的转换。
通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义.
mybatis支持类型处理器:
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | Boolean,boolean | 任何兼容的布尔值 |
ByteTypeHandler | Byte,byte | 任何兼容的数字或字节类型 |
ShortTypeHandler | Short,short | 任何兼容的数字或短整型 |
IntegerTypeHandler | Integer,int | 任何兼容的数字和整型 |
LongTypeHandler | Long,long | 任何兼容的数字或长整型 |
FloatTypeHandler | Float,float | 任何兼容的数字或单精度浮点型 |
DoubleTypeHandler | Double,double | 任何兼容的数字或双精度浮点型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的数字或十进制小数类型 |
StringTypeHandler | String | CHAR和VARCHAR类型 |
ClobTypeHandler | String | CLOB和LONGVARCHAR类型 |
NStringTypeHandler | String | NVARCHAR和NCHAR类型 |
NClobTypeHandler | String | NCLOB类型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB和LONGVARBINARY类型 |
DateTypeHandler | Date(java.util) | TIMESTAMP类型 |
DateOnlyTypeHandler | Date(java.util) | DATE类型 |
TimeOnlyTypeHandler | Date(java.util) | TIME类型 |
SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP类型 |
SqlDateTypeHandler | Date(java.sql) | DATE类型 |
SqlTimeTypeHandler | Time(java.sql) | TIME类型 |
ObjectTypeHandler | 任意 | 其他或未指定类型 |
EnumTypeHandler | Enumeration类型 | VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 |
mappers(映射配置)
通过resource加载单个映射文件
和上面一样1
2
3
4
5
6
7<configuration>
...
<!-- 加载映射文件 -->
<mappers>
<mapper resource = "sqlmap/UserMapper.xml"/>
</mappers>
</configuration>
通过mapper接口加载单个mapper
使用mapper映射的方式,光指定Java接口全限定名即可,但是xml要在同目录同名1
2
3
4
5<!-- 通过mapper接口加载单个 映射文件
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中
上边规范的前提是:使用的是mapper代理方法
-->
<mapper class="cn.xwmdream.Main.UserMapper"/>
按照上边的规范,将mapper.java和mapper.xml放在一个目录 ,且同名。
批量加载mapper(推荐使用)
和第二种方法一样,用package加载包名,则下面的所有mapper都会被加载
1
2
3
4
5
6<!-- 批量加载mapper
指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录中
上边规范的前提是:使用的是mapper代理方法
-->
<package name="cn.xwmdream.Main"/>需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中
输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型。
传递pojo的包装对象
需求
- 修改上面的插入用户
- 这个方法可以用来做多种条件的联合查询(可能包括用户信息、其它信息,比如商品、订单的)
定义包装类型对象
UserVo.java1
2
3
4
5
6
7
8
9
10public class UserVo {
private User usera;
public User getUser() {
return usera;
}
public void setUser(User usera) {
this.usera = usera;
}
//其它类型
}
- 针对上边需求,建议使用自定义的包装类型的对象。
- 在包装类型的pojo中将复杂的查询条件包装进去。
mapper.xml
在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。1
2
3
4
5
6<insert id="insertUser" parameterType="cn.xwmdream.test.UserVo">
<selectKey keyProperty="usera.id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address) value(#{usera.username},#{usera.birthday},#{usera.sex},#{usera.address})
</insert>
mapper.java
1 | public int insertUser(UserVo uservo); |
测试代码
1 | // mybatis配置文件 |
- 在mapper.xml中传入了包装类型对象,可以用属性名.属性名的方法使用参数
- 注意重名问题,比如和自定义名称重名
传递hashmap
Sql映射文件定义如下:1
2
3
4<!-- 传递hashmap综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
select * from user where id=#{id} and username like '%${username}%'
</select>
- 使用的是hashmap的key。
输出映射
- 输出映射就是返回值映射成什么对象的映射
- 分为两种,resultType(上面用过好几次了),resultMap(对resultType的增强)
resultType
映射成简单类型
int型
- 求总人数
映射文件
1 | <select id="selectCount" resultType="int"> |
接口以及调用
1 | //mapper接口 |
- 把sql查询出来的count的值以int类型返回
String型
- 求id为1的人名
映射文件
1 | <select id="findUserNameByid" parameterType="int" resultType="String"> |
接口以及调用
1 | //接口 |
- 把sql查询出来的username以一个String的值传回
自定义类型
- 求id为1的人的信息,如username,sex,address,birthday
自定义类型
1 | public class User { |
映射文件
1 | <select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User"> |
接口以及调用
1 | //接口 |
- 把查询出来的值以同名属性的方式赋值,username赋值给User.username
- 如果自定义对象中有构造方法,一定要留一个无参的构造方法
问题
- 没有select的值会赋值为默认空值
- select了但是名字不一样的值也会赋值为默认空值,上面案例中自定义属性为se,但是sql中是sex,最后的结果se为null
- 第二个问题的解决办法可以通过修改sql比如
1
select username,sex se,address from user where id=#{hhh};
在sql中把sex对应的名字改成se
也可以使用resultMap
resultMap
mybatis中使用resultMap完成高级输出结果映射。
在Mapper标签内定义一个resultMap
1 | <mapper namespace="cn.xwmdream.Main.UserMapper"> |
映射文件中使用resultMap
1 | <select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> |
- 此时再运行,sex便可以映射成自定义对象中的se
动态sql
什么是动态sql
- mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
需求
- 通过id查人信息,如果id为0则输出所有人信息
映射文件
1 | <select id="findUserById" parameterType="int" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> |
- 如果是基本类型,那么要用value代表那个值
- 上述代码如果传入的值不是0,比如是1,那么最后的sql是select username,sex,address from user where id=1;
- 如果传入的id是0,那么最后的sql是select username,sex,address,from user;即可找到所有的用户
如果传入的是自定义对象
1 | <select id="findUserById" parameterType="cn.xwmdream.test.User" resultType="cn.xwmdream.test.User" resultMap="userResultMap"> |
如果传入的User.id不是0且User.username不是null,最后的sql为
1
select user.username,sex,address from user where id=1 and username='user';
如果传入的id不是0,但是username是null,最后sql为
1
select user.username,sex,address from user where id=1;
sql片段
- 将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。方便程序员进行开发。
#### 定义sql片段1
2
3
4
5
6
7
8
9
10
11
12
13<mapper namespace="cn.xwmdream.Main.UserMapper">
<sql id="query_user_where">
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
</sql>
<sql id="two">
select username,sex,address from user
</sql>
</mapper>
调用
1 | <select id="findUserById" parameterType="cn.xwmdream.test.User" resultType="cn.xwmdream.test.User"> |
foreach
就是用foreach循环对传入的列表对象拼接成字符串1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- 使用 foreach遍历传入ids
collection:指定输入 对象中集合属性
item:每个遍历生成对象中
open:开始遍历时拼接的串
close:结束遍历时拼接的串
separator:遍历的两个对象中需要拼接的串
-->
<!-- 使用实现下边的sql拼接:
AND (id=1 OR id=10 OR id=16)
-->
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="or">
<!-- 每个遍历需要拼接的串 -->
id=#{user_id}
</foreach>
<!-- 实现 “ and id IN(1,10,16)”拼接 -->
<foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
<!-- 每个遍历需要拼接的串 -->
#{user_id}
</foreach>
- 第一个结果是AND (id=1 OR id=10 OR id=16)
- 第二个结果是and id IN(1,10,16)
高级用法
数据库
1 | CREATE TABLE `items` ( |
- 订单表:orders记录了用户所创建的订单(购买商品的订单)
- 订单明细表:orderdetail:记录了订单的详细信息即购买商品的信息
- 商品表:items记录了商品信息
- 用户表user:记录了购买商品的用户信息
数据
1 | insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02'); |
关联查询
一对一查询
需求
- 查询订单信息,关联查询创建订单的用户信息
创建bean类
User.java1
2
3
4
5
6
7public class User {
private Integer id;
private String username;
private String sex;
private String address;
//...getter&setter
}
Orders.java1
2
3
4
5
6
7
8
9public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
//...getter&setter
}
- Orders类是结果的映射,但是这个映射中有一个User对象,接下来就需要用resultMap来创建映射规则
mapper
1 | <mapper namespace="cn.xwmdream.mapper.MyMapper"> |
接口MyMapper.java1
2
3public interface MyMapper {
public List<Orders> findOrdersUserResultMap();
}
运行
1 | //mybatis配置文件 |
一对多查询
需求
- 查询订单及订单明细的信息。
sql语句
- 确定主查询表:订单表
- 确定关联查询表:订单明细表
- 在一对一查询基础上添加订单明细表关联即可。
1
SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id FROM orders,USER,orderdetail WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id
要求:
- 对orders映射不能出现重复记录。
- 在orders.java类中添加List
orderDetails属性。 - 最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。
bean类
创建Orderdetail.java1
2
3
4
5
6
7public class Orderdetail {
private Integer id;
private Integer itemsId;
private String itemsNum;
private Integer ordersId;
//...getter&setter
}
修改Orders.java1
2
3
4
5public class Orders {
//...多加一条属性
private List<Orderdetail> orderdetails;
///...getter&setter
}
mapper映射
1 | <mapper namespace="cn.xwmdream.mapper.MyMapper"> |
- 其中orderdetail.id的名字是id和之前的orders.id重名,所以要改名orderdetail.id orderdetail_id
小结
mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
使用resultType实现:
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
多对多查询
需求
- 查询用户及用户购买商品信息。
sql语句
查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:
orders、orderdetail、items
1 | SELECT orders.*,USER.username,USER.sex,USER.address,orderdetail.id orderdetail_id,orderdetail.items_id,orderdetail.items_num,orderdetail.orders_id,items.name items_name,items.detail items_detail,items.price items_price FROM orders,USER,orderdetail,items WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id; |
- 查询主体是用户表,一个用户有多个订单,一个订单有多个订单明细,一个订单明细有一个商品
bean类
User类1
2
3
4
5
6
7
8public class User {
private Integer id;
private String username;
private String sex;
private String address;
private List<Orders> ordersList;
//...getter&setter&toString
}
Orders类1
2
3
4
5
6
7
8
9public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private List<Orderdetail> orderdetails;
//...getter&setter&toString
}
Orderdetail类1
2
3
4
5
6
7
8public class Orderdetail {
private Integer id;
private Integer itemsId;
private String itemsNum;
private Integer ordersId;
private Item item;
//...getter&setter&toString
}
Item类1
2
3
4
5
6
7public class Item {
private Integer id;
private String detail;
private float price;
private String name;
//...getter&setter&toString
}
映射mapper
1 |
|
延迟加载
延迟加载:先从单表查询、需要时再从关联表去关联查询,不需要就不查询了,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。
例如要查询所有订单,并且提供查看订单用户的功能,但是并不是所有的订单都去看用户,只用点谁的订单才看谁的用户信息,这是就可以把用户信息来延迟加载
延迟加载配置
mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。
在mybatis核心配置文件(SqlMapConfig.xml)中配置:lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 | true|false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true|false | true |
- SqlMapConfig.xml
1
2
3
4
5
6
7
8
9<configuration>
<!-- 全局配置参数,需要时再设置 -->
<settings>
<!-- 打开延迟加载 的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载即按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
延迟加载例子
- 需求:查询所有订单,并查看订单用户
bean类
订单:Orders.java
1
2
3
4
5
6
7
8
9public class Orders {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
//getter&setter&toString
}用户:User.java
1
2
3
4
5
6
7public class User {
private Integer id;
private String username;
private String sex;
private String address;
//getter&setter&toString
}
mapper映射
MyMapper.java1
2
3
4public interface MyMapper {
public List<Orders> getAllOrders();
public User getUserById(int id);
}
MyMapper.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<mapper namespace="cn.xwmdream.mapper.MyMapper">
<resultMap type="cn.xwmdream.bean.Orders" id="OrdersUserResultMap">
<id column="id" property="id" />
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,如果findUserById不在本mapper中需要前边加namespace
column:订单信息中关联用户信息查询的列,是user_id,相当于是传入第二个sql的值
关联查询的sql理解为: SELECT orders.*, (SELECT username
FROM USER WHERE orders.user_id = user.id)username, (SELECT sex FROM USER
WHERE orders.user_id = user.id)sex FROM orders -->
<association property="user" javaType="cn.xwmdream.bean.User" select="getUserById" column="user_id">
</association>
</resultMap>
<select id="getUserById" parameterType="int" resultType="cn.xwmdream.bean.User">
select * from user where id=#{value}
</select>
<select id="getAllOrders" resultType="cn.xwmdream.bean.Orders" resultMap="OrdersUserResultMap">
select * from orders
</select>
</mapper>
运行
1 | //mybatis配置文件 |
- 刚开始调用getAllOrders只调用那一句select * from orders,不会调用所有的用户信息
- 当获取到的Orders对象去访问他的user属性时,才会去调用select * from user where id =?根据Orders的user_id获取到相应的用户信息
替代方法
- 其实就是可以先获取到所有的Orders,再根据需求调用他的User信息
- mybatis就是把这两种合成一个了,把User当成Orders的一个属性,当调用这个属性时再去请求User信息,不调用就不请求了
查询缓存
什么是查询缓存
- mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
- mybaits提供一级缓存,和二级缓存
- 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
- 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
- 如果缓存中有数据就不用从数据库中获取,大大提高系统性能
一级缓存
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
一级缓存应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。1
2
3
4
5
6
7service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
- 如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
二级缓存
原理
首先开启mybatis的二级缓存。
sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。
开启二级缓存
- SqlMapConfig.xml
1
2
3
4
5
6
7
8<configuration>
...
<!-- 全局配置参数,需要时再设置 -->
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
方法 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache进行全局性开/关设置。 | true false | true |
- mapper.xml
在mapper对namespace开启二级缓存1
2
3
4
5
6
7<mapper namespace="xxxx">
<!-- 开启本mapper的namespace下的二缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache type="xxx可以不写"/>
</mapper>
useCache配置
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。1
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache=”true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:1
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
二级应用场景
- 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
- 实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
二级缓存局限性
- mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
spring和mybatis整合
整合思路
- 需要spring通过单例方式管理SqlSessionFactory。
- spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)
- 持久层的mapper都需要由spring进行管理。
整合环境
- mybatis用到的jar
- spring用到的logger
- 数据库jdbc用到的jar
- spring用到的jar
- dbcp
- spring的tx包
- mybatis-spring
sqlSessionFactory
- 在applicationContext.xml配置sqlSessionFactory和数据源
- sqlSessionFactory在mybatis和spring的整合包下。
ApplicationContext.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 数据源,使用dbcp -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- sqlSessinFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载mybatis的配置文件 -->
<property name="configLocation" value="SqlMapConfig.xml" />
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 原始dao接口 -->
<bean id="userDao" class="cn.xwmdream.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
在src下创建一个properties文件,写入数据库的信息
db.properties1
2
3
4jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=x
用原始dao开发
配置SqlMapConfig.xml
1 |
|
配置User.xml
sqlmap/User.xml1
2
3
4
5
6
7
8
9
10
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType="cn.xwmdream.bean.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
创建bean类
1 | public class User { |
配置dao接口以及实现类
1 | public interface UserDao { |
测试代码
1 | ApplicationContext applicationContext= new ClassPathXmlApplicationContext("classpath:applicationContext.xml");; |
用mapper开发
创建mapper以及对应文件
- 要遵循规范,在同一个包下,同名
UserMapper.xml1
2
3
4
5
6
7
8
9
<mapper namespace="cn.xwmdream.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="cn.xwmdream.bean.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
UserMapper.java1
2
3public interface UserMapper {
public User findUserById(int id);
}
使用spring获取mapper
在ApplicationContext.xml增加bean1
2
3
4
5
6
7
8
9
10
11<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器中注册
遵循规范:将mapper.java和mapper.xml映射文件名称保持一致,且在一个目录 中
自动扫描出来的mapper的bean的id为mapper类名(首字母小写)
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包名
如果扫描多个包,每个包中间使用半角逗号分隔
-->
<property name="basePackage" value="cn.xwmdream.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
- 会把cn.xwmdream.mapper下所有符合条件的mapper添加到spring容器中
- 自动扫描出来的mapper的bean的id为mapper类名(首字母小写)
修改SqlMapConfig.xml
因为mapper类已经让spring扫描出来了,所以在SqlMapConfig中就不用扫描了
1
2
3
4
5
6
<configuration>
</configuration>就是啥都没
#### 运行1
2
3
4
5ApplicationContext applicationContext= new ClassPathXmlApplicationContext("classpath:applicationContext.xml");;
UserMapper userDao = (UserMapper) applicationContext.getBean("userMapper");
//调用userDao的方法
User user = userDao.findUserById(1);
System.out.println(user);
- 注意bean名首字母小写