>> Mybatis基础
中文文档:http://www.mybatis.org/mybatis-3/zh/configuration.html
百度文库文档笔记:https://wenku.baidu.com/view/a7e67cffcd22bcd126fff705cc17552707225e2d
- Mybatis是一个持久层的框架,持久层的框架都是对JDBC的封装;持久层开发就要写DAO;
- Mybatis是Apache下的顶级项目;后来托管到Googlecode,再后来托管到GitHub下https://github.com/Mybatis/Mybatis-3/releases;
- Mybatis让程序员将主要精力放在
SQL
上,通过Mybatis提供的映射方式,自由灵活生成满足需求的SQL语句(生成是半自动化,大部分需要程序员编写SQL);(Hibernate基本看不见SQL语句,都封装起来了) - Mybatis可以将向preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射);
>> Mybatis和Hibernate本质区别
- Hibernate:
- 是一个标准ORM框架(对象关系映射);入门门槛较高,不需要程序员写SQL语句,SQL语句自动生成;
- 对SQL语句进行优化、修改比较困难;
- 应用场景:适用于需求变化不多的中小型项目,比如:后台管理系统、erp、orm、oa等;
- Mybatis:
- 专注于SQL语句本身,需要程序员自己写SQL语句,SQL语句修改、优化比较方便;
- Mybatis是一个不完全的ORM框架,虽然程序员自己写SQL语句,Mybatis也可以实现映射:输入映射、输出映射;
- 适用场景:适用于需求变化较多的项目,比如:互联网项目(京东、淘宝等)
>> Mybatis开发dao的两种方法
- (1)原始的dao开发方法(类似于Hibernate开发dao,即程序员需要编写dao接口和dao实现类);
- (2)Mybatis所特有的方法:Mybatis的mapper接口代理开发方法;(mapper相当于dao接口)
>> 原生态jdbc编程中的问题总结
原生态jdbc程序(单独使用jdbc开发):
- 1、数据库连接,使用时就创建,不使用就立即释放,对数据库进行频繁的连接开启和关闭,造成数据库资源浪费(数据库用就拿来,不用就丢掉),影响数据库的性能;
设想:使用数据库连接池来管理数据库连接; - 2、将SQL语句硬编码到java代码中,若SQL语句修改,需要重新编译java代码,不利于系统维护;
设想:将SQL语句配置在xml配置文件中,即使SQL变化,也不需要对java代码进行重新编译; - 3、向prepareStatement中设置参数,对占位符号位置和设置参数值,硬编码到java代码中,不利于系统维护;
设想:将SQL语句、占位符、参数全部配置在xml中; - 4、从resultSet中遍历结果集时,存在硬编码,将获取的字段进行硬编码,不利于系统维护;
设想:将查询的结果集,自动映射成java对象(就像Hibernate所实现的);
>> Mybatis框架原理
首先创建配置文件,根据配置文件创建session工厂,通过工厂创建会话,通过会话操作数据库,最后连上数据库;
配置文件:
(1)全局配置文件SqlMapConfig.xml
(全局只有一个):里面配置数据源、事物等Mybatis的运行环境;
(2)映射配置文件mapper.xml
:主要配置sql语句,因为Mybatis的主要精力在SQL语句上;
在映射文件mapper.xml
里面配置sql语句,映射文件有多个;
【三大框架整合时,事物一般使用spring的事物控制,数据源一般使用第三方的连接池c3p0;而映射配置文件是Mybatis独有的;】根据配置文件创建SqlSessionFactory(会话工厂):
作用:创建SQLSession根据会话工厂创建SqlSession(会话):SqlSession是一个面向用户(程序员)的接口,用户通过session的方法操作数据库;
作用:操作数据库(发出SQL增删改查)Executor(执行器):是一个底层的封装对象,也是一个接口,有两个实现类:基本执行器、缓存执行器;
作用:SQLSession内部通过执行器操作数据库mappedstatement:Mybatis提供了一个底层的封装对象mappedstatement,这个底层的封装对象对操作数据库的信息进行了存储和封装,信息包括sql语句、输入参数、输出结果类型;
输入/出参数类型:java简单类型、hashmap、pojo自定义;
>> Mybatis - jar包介绍
>> 入门程序
~ 项目结构
src
文件夹:可以看做是classpath路径;
- 创建不同的包文件,包里面存放类文件;
config
文件夹:new Source Folder
,与src文件夹性质一样;
sqlmap
文件夹:存放映射配置文件;db.properties
文件:存放数据库连接所需信息,在全局配置文件里面通过<properties resource="db.properties"></properties>
引用里面的键值对;log4j.properties
:配置log4j
日志;SqlMapConfig.xml
:全局配置文件;
~ log4j.properties
在项目中创建Source Folder文件夹config,里面存放log4j.properties
日志文件,日志级别要设置成debug;
(日志文件可以与配置文件放在一起)
# Global logging configuration
# 在开发环境下日志级别要设置成debug,生产环境设置成info或error
log4j.rootLogger = debug,stdout
# Console output...
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
~ db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=123456
~ 全局配置文件 SqlMapConfig.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 resource="db.properties"> //从配置文件db.properties中读取数据库连接信息
<!--<property name="driver" value="com.mysql.jdbc.Driver"/> properties中还可以配置一些属性名和属性值 -->
</properties>
<!-- settings 全局配置参数,需要时再配置 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!-- 打开延迟加载的开关 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 将积极加载改为消极加载(按需要加载) -->
<setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存总开关:默认为true,写上方便代码阅读、维护 -->
</settings>
<!-- typeAliases 类型别名 -->
<!-- 针对单个别名定义 -->
<!-- type:类型的路径; alias:别名 -->
<typeAliases>
<typeAlias type="cn.itcast.mybatis.po.User" alias="user" />
</typeAliases>
<!-- 批量别名定义 -->
<!-- name:指定包名,mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写或小写都可以) -->
<typeAliases>
<package name="cn.itcast.mybatis.po"/> // 定义别名之后,必须使用别名,不能加全限定名;
</typeAliases>
<environments default="development"> <!-- 和spring整合后 environments将废除 -->
<environment id="development">
<transactionManager type="JDBC" /> <!-- 使用JDBC事务管理,事务控制由mybatis -->
<dataSource type="POOLED"> <!-- 数据库连接池,由mybatis管理 -->
<property name="driver" jdbc.value="${driver}" />
<property name="url" jdbc.value="${url}" />
<property name="username" jdbc.value="${username}" />
<property name="password" jdbc.value="${password}" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件:和spring整合后,使用mapper扫描器,这个就不需要再设置了; -->
<mappers>
<!-- (1)使用相对于路径的资源 -->
<mapper resource="sqlmap/User.xml" /> // 通过resource属性加载单个映射文件(一次加载一个映射文件)
<!-- (2)使用完全限定路径 -->
<mapper url="E:\WorkSpace\mybatis\config\sqlmap\User.xml"/>
<!-- (3)通过mapper接口加载单个映射文件 -->
<!-- 遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在同一目录中;
上边规范的前提是:使用的是mapper代理方法; -->
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
<!-- (4)批量加载mapper -->
<!-- name属性:指定mapper接口的包名,mybatis自动扫描包下所有mapper接口进行加载;
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在同一目录中;
上边规范的前提是:使用的是mapper代理方法; -->
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
</configuration>
~ 映射文件 User.xml
映射文件:Hibernate的映射是ORM对象关系映射,将java对象与数据库的二维表进行映射,而Mybatis的映射具体化了,有输入映射和输出映射;
映射文件命名:
- 原始的ibatis命名:
Xxx.xml
; - mapper代理开发映射文件命名:
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">
<!-- namespace命名空间,作用是对SQL进行分类化管理,简单理解就是SQL隔离,隔离不同的数据表; -->
<!-- 注意:使用mapper代理方法开发,namespace有特殊的重要的作用:namespace等于mapper接口地址 -->
<mapper namespace="test"> <!-- 在映射文件中配置很多SQL语句 -->
<!-- 定义resultMap
将SELECT id id_,username username_ FROM user 和User类中的属性作为一个映射关系;
type:resultMap最终映射的java对象类型;(可以使用别名)
id:resultMap的唯一标识;
-->
<resultMap type="user" id="userResulyMap">
<!-- id表示查询结果集中的唯一标识;
column:查询出来的列名;
property:type指定的pojo类型中的属性名;
最终resultMap对column和property作一个映射关系(对应关系) -->
<id column="id_" property="id"/>
<!-- result:对普通属性名进行映射; -->
<result column="username_" property="username"/>
</resultMap>
<!-- 使用resultMap进行输出映射:
resultMap:指定定义的resultMap的id,
如果这个resultMap在其他的mapper文件中,前面需要加上namespace; -->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResulyMap">
SELECT id id_,username username_ FROM user WHERE id=#{value}
</select>
<!-- 需求:通过id查询用户表中的记录,返回一条记录; -->
<!-- 通过select标签执行数据库查询语句:
id:标识映射文件中的sql,将sql语句封装到mappedStatement对象中,so将id称为statement的id;
parameterType:指定输入参数类型(与数据库中表的字段对应),这里指定int型,是因为数据库表里字段是int;
resultType:指定sql输出结果的 所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象;
值是po类的全限定名;
#{}:表示一个占位符号;
#{id}:id表示接受输入的参数,参数名称就是id;若输入参数是简单类型,#{}中参数名可以任意,可使用value或其他名称;
-->
<select id="findUserById" parameterType="int" resultType="cn/itcast/mybatis/po/User">
SELECT * FROM user WHERE id=#{id}
</select>
<!-- 需求:根据用户名模糊查询用户信息,可能返回一条或多条记录; -->
<!-- resultType:指定就是单条记录所映射的java对象类型;
${}:表示一个拼接符号;表示拼接sql串,将接收到的参数的内容不加任何修饰的拼接在sql串中;
${value}:接收输入的参数的内容,如果传入的是简单类型,${}中只能使用value;
-->
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM user WHERE username LIKE '%${value}%'
</select>
<!-- 需求:添加用户(1) -->
<!-- parameterType:指定输入的参数类型是pojo(包括用户信息);
#{..}中指定pojo的属性名,接收到pojo对象的属性值,mybatis通过OGNL获取对象的属性值;
-->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
INSERT into user(username,sex,birthday,address) value(#{username},#{sex},#{birthday},#{address})
</insert>
<!-- 需求:添加用户(2) - 自增主键返回:使用LAST_INSERT_ID()方法; -->
<!-- mysql自增主键,执行insert提交之前自动生成一个自增主键;
通过mysql的 USER_INSERT_ID() 函数获取到刚插入记录的自增主键;
是在insert之后调用查询函数;
keyProperty:将查询到的主键值设置到parameterType指定的对象的哪个属性;
order:指定SELECT USER_INSERT_ID()相对于insert语句的执行顺序;
resultType:指定SELECT USER_INSERT_ID()的结果类型;
-->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT into user(username,sex,birthday,address) value(#{username},#{sex},#{birthday},#{address})
</insert>
<!-- 需求:添加用户(3) - 非自增主键返回:使用uuid()方法; -->
<!-- 使用mysql的uuid()函数生成主键,需要修改表中id字段类型为string,长度设置成35位;
先通过uuid()查询到主键,将主键设置到user对象的id属性中;然后在insert执行时,从user对象中取出id属性值;
是在insert之前调用查询函数;
-->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
SELECT uuid()
</selectKey>
INSERT into user(id,username,sex,birthday,address) value(#{id},#{username},#{sex},#{birthday},#{address})
</insert>
<!-- 需求:根据id删除用户; 需要输入id值; -->
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE FROM user WHERE id=#{id}
</delete>
<!-- 需求:根据id更新用户; 需要传入用户id,和用户的更新信息; -->
<!-- parameterType:指定user对象,包括id和更新信息;【id必须存在!】
#{id}:从输入user对象中获取id属性值;
-->
<update id="updateUser" parameterType="cn.itcast.mybatis.po.User">
UPDATE user SET username=#{username},sex=#{sex},birthday=#{birthday},address=#{address}
WHERE id=#{id}
</update>
<!-- 用户信息综合查询
#{userCustom.sex}:取出pojo包装对象中的性别的值
'%{userCustom.username}%':取出pojo包装对象中的用户名称
-->
<select id="findUserList" parameterType="cn.itcast.mybatis.po.UserQueryVo"
resultType="cn.itcast.mybatis.po.UserCustom">
SELECT * FROM user WHERE user.sex = #{userCustom.sex} AND user.username LIKE '${userCustom.username}%'
</select>
<!-- 用户信息综合查询总数
parameterType:指定输入类型和findUserList一样;
resultType:输出结果类型;
-->
<select id="findUserCount" parameterType="cn.itcast.mybatis.po.UserQueryVo" resultType="int">
SELECT COUNT(*) FROM user WHERE user.sex = #{userCustom.sex} AND
user.username LIKE '%${userCustom.username}%'
</select>
<!-- 动态sql查询 - if判断 -->
<select id="findUserList" parameterType="cn.itcast.mybatis.po.UserQueryVo"
resultType="cn.itcast.mybatis.po.UserCustom">
SELECT * FROM user
<where> <!-- where可以自动去掉 -->
<if test="userCustom!=null">
<if test="userCustom.sex!=numm and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=numm and userCustom.username!=''">
and user.username LIKE '%${userCustom.username}%'
</if>
</if>
</where>
</select>
<select id="findUserCount" parameterType="cn.itcast.mybatis.po.UserQueryVo" resultType="int">
SELECT COUNT(*) FROM user
<where> <!-- where可以自动去掉 -->
<if test="userCustom!=null">
<if test="userCustom.sex!=numm and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=numm and userCustom.username!=''">
and user.username LIKE '%${userCustom.username}%'
</if>
</if>
</where>
</select>
<!-- 动态sql查询 - sql片段 -->
<!-- 定义sql片段
id:sql片段的唯一标识;
经验:(1)是基于单表来定义sql片段的,这样的话这个sql片段可重用性才高;
(2)在SQL片段中不要包括where-->
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=numm and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=numm and userCustom.username!=''">
and user.username LIKE '%${userCustom.username}%'
</if>
</if>
</sql>
<select id="findUserList" parameterType="cn.itcast.mybatis.po.UserQueryVo"
resultType="cn.itcast.mybatis.po.UserCustom">
SELECT * FROM user
<where> <!-- where可以自动去掉 -->
<!-- 引用sql片段的id,如果refid指定的id不在本mapper中,需要在前边加上namespace; -->
<include refid="query_user_where"></include>
<!-- 在这里还可以引用其他sql片段,所有sql片段中不能写where; -->
</where>
</select>
<!-- 动态sql查询 - foreach -->
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=numm and userCustom.sex!=''">
and user.sex = #{userCustom.sex}
</if>
<if test="userCustom.username!=numm and userCustom.username!=''">
and user.username LIKE '%${userCustom.username}%'
</if>
<if test="ids!=null">
<!-- 使用foreach遍历传入ids:
collection:指定输入对象中集合属性;
items:每个遍历生成的对象;
open:开始遍历时拼接的串;
close:结束遍历时拼接的串;
separator:遍历的两个对象中需要拼接的串; -->
<!-- (1) 实现sql “ AND (id=1 OR id=2 OR id=3) ”的拼接 -->
<foreach collection="ids" item="user_id" open="AND (" close=")" separator="OR">
id=#{user_id}
</foreach>
<!-- (2) 实现sql “ and id IN(1,2,3) ”的拼接 -->
<foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
#{user_id}
</foreach>
</if>
</if>
</sql>
</mapper>
~ po类 - User
public class User {
private int id; // 用户id
private String username; // 用户姓名
private String sex; // 用户性别
private Date birthday; // 用户生日
private String address; // 用户地址
set/get.......
}
~ 测试类 - MybatisFirst
public class MybatisFirst {
@Test // 根据用户id(主键)查询用户信息
public void findUserById() throws IOException {
// mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建会话工厂,传入mybatis的配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通过会话工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession操作数据库
// 第一个参数:映射文件中statement的id,等于=namespace.statementID
// 第二个参数:指定和映射文件中所匹配的parameterType类型的参数
// sqlSession.selectOne 返回结果是 与映射文件中所匹配的resultType类型的对象
// selectOne:表示查询出一条记录进行映射;若使用selectOne可以实现,使用selectList也可实现(List中只有一个对象)
User user = sqlSession.selectOne("test.findUserById", 1);
System.out.println(user);
sqlSession.close(); // 关闭资源
}
@Test //根据用户名称模糊查询用户信息
public void selectByName() throws IOException{
// 模板代码...... - 创建sqlSession;
// selectList:表示查询出一个列表(多条记录)进行映射;如果使用selectList查询多条记录,不能使用selectOne;
// List中的user和映射文件中的resultType所指的类型一致;
List<User> list = sqlSession.selectList("test.findUserByName", "小明");
System.out.println(list);
sqlSession.close(); // 关闭资源
}
@Test // 添加用户
public void insertUserTest() throws IOException{
// 模板代码...... - 创建sqlSession;
User user = new User(); // 创建user对象
user.setUsername("墨遥"); // 设置user对象的属性
user.setSex("男");
user.setBirthday(new Date()); //获取系统当前日期
user.setAddress("天津蓟县");
sqlSession.insert("test.insertUser", user); // 执行插入操作
sqlSession.commit(); // 提交事务
// 获取用户信息主键
// (1)自增主键返回:使用LAST_INSERT_ID()方法;
// (2)非自增主键返回:使用uuid()方法;
System.out.println(user.getId());
sqlSession.close(); // 关闭资源
}
@Test // 删除用户信息
public void deleteUserTest() throws IOException{
// 模板代码...... - 创建sqlSession;
sqlSession.delete("test.deleteUser", 15); //传入id删除用户信息
sqlSession.commit(); // 提交事务
sqlSession.close(); // 关闭资源
}
@Test // 更新用户信息
public void updateUserTest() throws IOException{
// 模板代码...... - 创建sqlSession;
User user = new User();
user.setId(6); // 更新数据库记录,必须设置id属性的值!!
user.setUsername("温暖"); //设置要更新的属性的值
user.setSex("女");
user.setBirthday(new Date());
user.setAddress("A市");
sqlSession.update("test.updateUser", user); // 执行更新操作
sqlSession.commit(); // 提交事务
sqlSession.close(); // 关闭资源
}
}
~ 占位符号#{}
和拼接符号${}
#{}
:表示一个占位符号;
#{}
接收输入参数,类型可以是简单类型,pojo,hashmap;- 如果接收简单类型(int、String等),
#{}
中参数名可以任意,可以写成value或其他名称;
#{id}
:其中的id表示接受输入的参数,参数名称就是id; - 如果接收pojo对象,通过OGNL读取对象中的属性值,通过
属性值.属性值.属性值...
的方式获取对象的属性值;
${}
:表示一个拼接符号;【使用${}
拼接sql串,会引起sql注入,不推荐使用】
- 表示拼接sql串,将接收到的参数的内容不加任何修饰的拼接在sql串中;
${}
接收输入参数,类型可以是简单类型,pojo,hashmap;- 如果接收简单类型,
${}
中只能写成value; - 如果接收pojo对象值,通过OGNL读取对象中的属性值,通过
属性值.属性值.属性值...
的方式获取对象的属性值;
>> SqlSession使用范围
- SQLSessionFactoryBuilder
- 通过SQLSessionFactoryBuilder创建会话工厂SQLSessionFactory;
- 将SQLSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SQLSessionFactoryBuilder;
- 在创建SQLSessionFactory时,因为SQLSessionFactory是使用单例模式管理的,值创建一个实例,so只需new一次SQLSessionFactoryBuilder即可;
- SQLSessionFactory
- 通过SQLSessionFactory创建SqlSession;
- 使用单例模式管理SQLSessionFactory(工厂一旦创建,使用一个实例)
- 将来Mybatis和Spring整合后,使用spring的IOC容器很容易实现单例管理模式 来管理SQLSessionFactory;
- SqlSession
- SqlSession是一个面向用户(程序员)的接口;
- SqlSession中提供了很多操作数据库的方法:selectOne(返回单个对象)、selectList(返回单个或多个对象)……
- SqlSession是线程不安全的,因为在SqlSession实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性;
- SqlSession的最佳应用场合在方法体内,定义成局部变量使用;
>> 原始的dao开发方法
思路:类似于Hibernate开发dao,程序员需要写Dao接口和Dao实现类;需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession;
~ 项目结构
cn.itcast.mybatis.dao
包中创建UserDao接口和接口实现类;
~ UserDao.java 接口
public interface UserDao { // 用户管理 的 dao 接口
public User findUserByID(int id) throws Exception; // 根据id查询用户信息
public void insertUser(User user) throws Exception; // 添加用户信息
public void deleteUser(int id) throws Exception; // 删除用户信息
}
~ UserDaoImpl.java 接口实现类
public class UserDaoImpl implements UserDao { // dao的接口实现类,需要向dao实现类中注入SqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
// 不能在这里定义SqlSession,因为它是线程不安全的,需要定义在方法体内;
// SqlSession sqlSession = sqlSessionFactory.openSession();
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { // 构造方法,在这里注入SqlSessionFactory
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public User findUserByID(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建SqlSession
User user = sqlSession.selectOne("test.findUserById", id); // 执行查询操作
sqlSession.close(); // 释放资源
return user; // 返回user对象
}
@Override
public void insertUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建SqlSession
sqlSession.insert("test.insertUser1", user); // 执行插入操作
sqlSession.commit(); // 提交事务
sqlSession.close(); // 关闭资源
}
@Override
public void deleteUser(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建SqlSession
sqlSession.delete("test.deleteUser", id); // 执行删除操作
sqlSession.commit(); // 提交事务
sqlSession.close(); // 关闭资源
}
}
~ UserDaoImplTest 测试类
public class UserDaoImplTest {
private SqlSessionFactory sqlSessionFactory;
@Before // 此方法是在执行测试之前执行;创建SqlSessionFactory;
public void setUp() throws Exception {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserByID() throws Exception {
UserDao userDao = new UserDaoImpl(sqlSessionFactory); // 创建UserDao对象
User user = userDao.findUserByID(1); // 调用UserDao的方法
System.out.println(user);
}
@Test
public void testInsertUser() throws Exception {
UserDao userDao = new UserDaoImpl(sqlSessionFactory); // 创建UserDao对象
User user = new User(); // 创建user对象
user.setUsername("墨遥1");
user.setSex("男");
user.setBirthday(new Date());
user.setAddress("天津蓟县");
userDao.insertUser(user); // 执行插入操作
}
@Test
public void testDeleteUser() throws Exception {
UserDao userDao = new UserDaoImpl(sqlSessionFactory); // 创建UserDao对象
userDao.deleteUser(1); // 执行删除操作
}
}
~ 原始dao开发的问题总结
- 1、dao接口实现类中存在大量模板代码;设想能否将这些代码提取出来,大大减轻程序员的工作量;
- 2、调用SqlSession方法时将statement的id硬编码了;
- 3、调用SqlSession方法时传入的变量,由于SqlSession方法使用泛型,即使变量类型传入错误,在编译阶段也不会报错,不利于程序员开发;
>> mapper代理方法的dao开发方法
Mybatis所特有的方法:Mybatis的mapper接口代理开发方法;
程序员需要编写 mapper.xml
和 mapper接口(相当于Dao接口);
~ 项目结构
~ mapper代理开发规范
程序员编写mapper接口需要遵循一些开发规范,mybatis就可以自动生成mapper接口实现类代理对象;
开发规范:
- 1、在
mapper.xml
中 namespace 等于 mapper 接口地址:
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
- 2、
mapper.java
接口中的方法名和mapper.xml
中statement的id一致; - 3、
mapper.java
接口中的方法输入参数类型和mapper.xml
中statement的parameterType指定的类型一致; - 4、
mapper.java
接口中的方法返回值类型和mapper.xml
中statement的resultType指定的类型一致;
总结:以上代码规范主要对下面的代码进行统一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser1", user);
等……
~ UserMapper.java 接口
public interface UserMapper { // 用户管理 的 mapper 接口,相当于dao接口【比dao接口多一些规范】
public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception; // 用户信息综合查询
public User findUserById(int id) throws Exception; // 根据id查询用户信息
public List<User> findUserByName(String name) throws Exception; // 根据用户名模糊查询用户信息
public void insertUser(User user) throws Exception; // 添加用户信息
public void deleteUser(Integer id) throws Exception; // 删除用户信息
}
~ 映射文件 UserMapper.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">
<!-- namespace命名空间,作用是对SQL进行分类化管理,简单理解就是SQL隔离,隔离不同的数据表; -->
<!-- 注意:使用mapper代理方法开发,namespace有特殊的重要的作用:namespace等于mapper接口地址 -->
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 开启mapper的namespace下的二级缓存; -->
<!-- type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache;
要和ehcache整合,需要配置type为ehcache的接口类型; -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE id=#{id}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM user WHERE username LIKE '%${value}%'
</select>
............
</mapper>
~ UserDaoImplTest 测试类
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before // 此方法是在执行测试之前执行
public void setUp() throws Exception { // 创建SqlSessionFactory
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
// mapper代理的方法,没有自己写类,so没有办法new对象;
// 通过会话工厂得到会话:传统的dao开发是在方法体内得到会话,mapper代理开发是在方法体外面的到,
// 通过这个方法体外部得到的会话,创建一个mapper的代理对象:sqlSession.getMapper(UserMapper.class);
@Test
public void testFindUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建方法体外部会话
// 创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(2); // 调用userMapper的方法
sqlSession.close(); //关闭资源
System.out.println(user);
}
@Test
public void testFindUserByName() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findUserByName("小明");
sqlSession.close();
System.out.println(list);
}
@Test
public void testInsertUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("墨xiaobai");
user.setSex("男");
user.setBirthday(new Date());
user.setAddress("天津蓟县");
userMapper.insertUser(user);
sqlSession.close();
}
@Test
public void testDeleteUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(2);
sqlSession.close();
}
}
~ mapper代理方法的dao开发的问题总结
- (1)代理对象内部调用selectOne或selectList问题:
- 如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库;
- 如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库;
- (2)mapper接口方法参数只能有一个是否会影响系统开发:否
- mapper接口方法参数只能有一个,系统是否不利于扩展维护:否
- 系统框架中,dao层的代码是被业务层公用的,即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求;
- 注意:持久层方法的参数可以使用包装类型、map…,service方法中不建议使用包装类型(不利于业务层的扩展)
~ 全局配置文件 SqlMapConfig.xml详解
mybatis的全局配置文件SqlMapConfig.xml,配置内容如下:
~ (1)properties(属性)
将数据库连接参数单独配置在db.properties
中,只需要在SqlMapConfig.xml
中加载db.properties
的属性值,在SqlMapConfig.xml
中就不需要对数据库连接参数进行硬编码了;
将数据库连接参数只配置在db.properties
中,原因:方便对参数进行统一管理,其他xml
可以引用该db.properties
;
Mybatis将按照下面的顺序来加载属性:
- (1)在properties元素体内定义的属性首先被读取;
- (2)然后会读取properties元素中resource或url加载的属性(db.properties文件中的属性),它会覆盖已读属性的同名属性;
- (3)最后读取parameterType传递的属性,它会覆盖已读的同名属性;
建议:不要在properties元素体内添加任何属性值,只将属性值定义在db.properties文件中;
在db.properties文件中定义的属性名要有一定的特殊性:xxx.xxx.xxx
~ (2)setting(全局配置参数)
mybatis框架在运行时可以调整一些运行参数:开启二级缓存、开启延迟加载…
全局参数将会影响mybatis的运行行为,So需要时才加,不需要不要加,否则,如果加不对,会影响mybatis的执行;
~ (3) typeAliases(类型别名)【重点】
在mapper.xml
中,定义很多的statement,statement需要parameterType指定输入参数的类型,需要resultType指定输出结果的映射类型;
如果在指定类型是输入类型全域名,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml
中通过定义别名,方便开发;
mybatis默认支持一些别名,自己定义的pojo需要自定义别名:
- 针对单个别名定义:
在主配置文件中定义别名:
在映射文件user.xml
文件中引用别名:
- 批量别名定义:
~ (4) typeHandlers(类型处理器)
mybatis中通过typeHandles类型处理器完成jdbc类型和java类型的转换;通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义;
~ (5)objectFactory(对象工厂)【了解】
~ (6)plugins(插件) 【了解】
~ (7)environments(环境集合属性对象)【废弃】
~ (8)environment(环境子属性对象)【废弃】
~ (9)transactionManager(事务管理)【废弃】
~ (10)dataSource(数据源)【废弃】
~ (11)mapper(映射器)
- 使用相对于路径的资源:
- 使用完全限定路径:
- 使用mapper接口类路径 - 加载单个mapper:
- 使用mapper接口类路径 - 批量加载mapper:
注意:使用mapper接口类路径加载mapper时需要将mapper接口类与mapper.xml放到同一目录下:
>> 输入映射 - pojo包装类型
- Vo:视图层的对象;这个对象能从页面一直传到持久层;
- Po:持久层的对象
- Pojo:自定义的,类似于Po、Vo综合体的简单的JavaBean;
通过ParameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型;
~ 传递Pojo的包装对象
需求:完成用户信息的综合查询,需要传入的查询条件很复杂(可能包括用户信息、其他信息,比如商品、订单的信息)
解决:针对上边需求,建议使用自定义的包装类型的pojo,在包装类型的pojo中将复杂的查询条件包装进去;
基类User类是通过User表创建的Po,一般是通过工具自动生成,建议内容不要修改,如果需要对User进行扩展,创建一个UserCustom的扩展类;UserCustom类继承User类; 在包装类UserQueryVo 里面对User类或者userCustom扩展类进行包装;
// (1)User类:基类
public class User {
private int id; // 用户id
private String username; // 用户姓名
private String sex; // 用户性别
private Date birthday; // 用户生日
private String address; // 用户地址
// set/get方法
}
// (2)UserCustom 类:用户的扩展类
public class UserCustom extends User {
// 扩展用户User类的信息,在这里面离不开用户的信息;
}
// (3)UserQueryVo :包装类型
public class UserQueryVo { // 在这里包装所需要的查询条件
private UserCustom userCustom; // 用户查询条件; 可以包装扩展类
// 也可以包装基类User类
// 还可以包装其他的查询条件:订单、商品...
// set/get方法
}
~ UserMapper.xml 映射文件
在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂的关联查询)
~ UserMapper.java 接口
~ 测试类
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before // 此方法是在执行测试之前执行
public void setUp() throws Exception {
// 创建SqlSessionFactory
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserList() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建包装对象,设置查询条件
UserQueryVo userQueryVo = new UserQueryVo();
UserCustom userCustom = new UserCustom();
userCustom.setSex("男");
userCustom.setUsername("墨");
userQueryVo.setUserCustom(userCustom);
List<UserCustom> list = userMapper.findUserList(userQueryVo); // 调用userMapper的方法
System.out.println(list);
sqlSession.close();
}
}
>> 输出映射 - resultType、resultMap
mybatis中输出映射有两种方式:resultType、resultMap
~ resultType
- 使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功;
- 如果查询出来的列名和pojo中的属性名全部不一致,查询出来有记录,但是没有创建pojo,所以所有列的值都为空;
- 只要查询出来的列名和pojo中的属性名有一个一致,就会创建对象,没有名称不一致的列的值都是空;
resultType可以输出pojo对象、输出pojo列表(List)、简单类型、hashmap类型;
- 输出简单类型:
- 需求:用户信息的综合查询列表总数,通过查询总数和用户综合查询列表,才可以实现分页查询;
UserMapper.xml
文件:
UserMapper.java
文件:
UserMapperTest.java
测试文件:
- 小结:查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射;
- 输出pojo对象和pojo列表:
- 不管是输出的pojo单个对象,还是一个列表(list中包括pojo),在
mapper.xml
文件中resultType指定的类型是一样的;在mapper.java
指定的方法返回值类型不一样; - 1、输出单个pojo对象,方法返回值是单个对象类型;
- 2、输出pojo对象list,方法返回值是
List<pojo>
; - 生成的动态代理对象中是根据mapper方法的返回值类型确定是调用selectOne(返回单个对象调用),还是selectList(返回集合对象调用);
- 不管是输出的pojo单个对象,还是一个列表(list中包括pojo),在
- 输出hashmap:输出pojo对象可以改用hashmap输出类型,将输出的字段名称作为map的key,value为字段值;
~ resultMap
mybatis中使用resultMap完成高级输出结果映射(一对一,一对多,多对多);
resultMap使用方法:如果查询出来的列名和pojo属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射;
- 将下边的sql使用User完成映射:User类中属性名和查询出来的列名不一致;
SELECT id id_,username username_ FROM user WHERE id=#{value};
- 在
UserMapper.xml
中定义resultMap:
- 在
UserMapper.xml
中使用resultMap作为statement的输出映射类型:
UserMapper.java
文件:
UserMapperTest.java
测试文件:
- 小结:
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功;
如果查询出来的列名与pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系;
>> 动态SQL
**什么是动态SQL:**mybatis核心是对sql语句进行灵活操作,通过对表达式进行判断,对sql进行灵活拼接、组装;
~ if判断
- 需求:用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql:对查询条件进行判断,如果输入参数不为空才进行查询条件的拼接;
UserMapper.xml
文件:
UserMapper.java
文件:
UserMapperTest.java
测试文件:
~ sql片段
- 需求:将上边实现的动态sql判断代码块抽取出来,其他的statement就可以引用sql片段,方便程序员进行开发;
- 在
UserMapper.xml
文件中定义sql片段:
- 在
UserMapper.xml
文件中定义的statement中引用sql片段:
UserMapperTest.java
测试文件:同上
~ foreach
向sql传递数组或List,mybatis使用foreach解析;
- 需求:在用户查询列表和查询总数的statement中增加多个id输入查询;
- 两种方法:
(1)SELECT * FROM USER WHERE id=1 or id=2 or id=3
(2)SELECT * FROM USER WHERE id IN(1,2,3)
- 在输入参数类型的包装类UserQueryVo类中添加
List<Integer> ids
属性,并添加set/get方法,用来传入多个id;
UserMapper.xml
文件编写foreach的sql片段:WHERE user.username LIKE '%小明%' AND (id=1 OR id=2 OR id=3)
在查询条件中,查询条件定义成一个SQL片段,需要修改SQL片段;
UserMapperTest.java
测试文件:
>> Mybatis 基础总结
在映射文件中通过parameterType指定输入参数的类型;
在映射文件中通过resultType指定输出结果的类型;
#{}
:表示一个占位符号;
${}
:表示一个拼接符号,会引起sql注入,不推荐使用;
selectOne:表示查询出一条记录进行映射;如果使用selectOne可以实现,使用selectList也可以实现;(List中只有一个对象)
selectList:表示查询出一个列表(多条记录)进行映射;如果使用selectList查询多条记录,不能使用selectOne;
>> 案例:订单商品数据模型
~ 数据模型分析思路
- 1、分析每张表的数据内容:分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程;
- 用户表user:记录购买商品的信息;
- 订单表orders:记录用户所创建的订单(购买商品的订单)
- 订单明细表orderdetail:记录了订单的详细信息,即购买商品的信息;
- 商品表items:记录了商品信息;
- 2、分析每张表的重要的字段的设置:非空字段、外键字段;
- 3、分析数据库级别的表与表之间的关系:外键关系;
- 4、分析表与表之间的业务关系:在分析表与表之间的业务关系时一定要建立在某个业务的意义的基础上去分析;
~ 数据模型的分析
表与表之间的业务关系:在分析表与表中间的业务关系时,需要建立在某个业务意义基础上去分析;
(1)先分析数据级别之间有关系的表之间的业务关系:
- user和orders:
user --> orders
:一对多;一个用户可以创建多个订单;
orders --> user
:一对一;一个订单只由一个用户创建; - orders和orderdetail:
orders --> orderdetail
:一对多;一个订单可以包含多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录;
orderdetail --> orders
:一对一;一个订单明细只能包含在一个订单中; - orderdetail和items:
orderdetail --> items
:一对一;一个订单明细只对应一个商品信息;
items --> orderdetail
:一对多;一个商品可以包含在多个订单明细中;
(2)再分析数据库级别没有关系的表之间是否有业务关系:
orders和items:多对多;两者之间可以通过orderdetail表建立关系;
>> 高级映射
~ 项目结构
~ 主配置文件 SqlMapConfig.xml
<configuration>
<properties resource="db.properties"></properties> <!-- 加载属性文件 -->
<settings>......</settings> <!-- settings 全局配置参数,需要时再配置 -->
<typeAliases><package name="cn.itcast.mybatis.po" /></typeAliases> <!-- 别名 -->
<environments default="development">......</environments> <!-- 连接数据库 -->
<mappers><package name="cn.itcast.mybatis.mapper" /></mappers> <!-- 加载映射文件 -->
</configuration>
~ 映射文件:OrdersMapperCustom.xml
<mapper namespace="cn.itcast.mybatis.mapper.OrdersMapperCustom">
<!-- (2)一对一: 查询订单关联查询用户信息,使用resultMap:
将整个查询结的结果映射到 cn.itcast.mybatis.po.Orders 中
-->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrderUserResultMap">
<!-- 配置映射的订单信息:
id:指定查询列中的唯一标识,订单信息中的唯一标识;如果有多个列组成唯一标识,则需配置多个id;
column:订单信息的唯一标识列;
property:订单信息的唯一标识列所映射到Orders类中的属性; -->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 配置映射的关联的用户的信息 :
association:用于映射关联查询单个对象的信息;
property:要将关联查询的用户信息映射到Orders类中的那个属性;
JavaType:关联查询的用户信息的类的全限定名; -->
<association property="user" javaType="cn.itcast.mybatis.po.User">
<!-- id:关联查询用户的唯一标识; -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
<!-- (3)一对多: 查询订单关联查询用户及订单明细,使用resultMap -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersAndOrderDetailResultMap"
extends="OrderUserResultMap">
<!-- 订单信息 -->
<!-- 用户信息 -->
<!-- 使用extends继承,不用再在resultMap中配置订单信息和用户信息的映射; -->
<!-- 订单明细信息 -->
<!-- 一个订单关联查询出了多条明细,要使用collection进行映射;
collection:对关联查询到的多条记录映射到集合对象中;
property:将关联查询到的多条记录映射到cn.itcast.mybatis.po.Orders哪个属性;
ofType:指定映射到List集合属性中的对象的pojo类型; -->
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id column="orderdetail_id" property="id" />
<result column="orders_id" property="orderId" />
<result column="items_id" property="itemsId" />
<result column="items_num" property="itemsNum" />
</collection>
</resultMap>
<!-- (4)多对多: 查询用户及用户够买的商品信息,使用resultMap -->
<resultMap type="cn.itcast.mybatis.po.User" id="UserAndItemsResultMap">
<!-- 用户信息 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<!-- 订单信息:一个用户对应多个订单,使用collection映射; -->
<collection property="orders" ofType="cn.itcast.mybatis.po.Orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 订单明细信息:一个订单包括多个明细,使用collection映射; -->
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result column="orders_id" property="orderId"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<!-- 商品信息:一个订单明细对应一个商品,使用association映射; -->
<association property="items" javaType="cn.itcast.mybatis.po.Items">
<id column="items_id" property="id"/>
<result column="items_name" property="name"/>
<result column="items_price" property="price"/>
<result column="items_detail" property="detail"/>
</association>
</collection>
</collection>
</resultMap>
<!-- (5)延迟加载:查询订单关联查询用户信息,用户信息需要延迟加载 -->
<resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserLazyLoadingResultMap">
<!-- 对订单信息进行映射配置 -->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
要使用findUserById完成根据用户id(user_id)用户信息的查询,
如果findUserById不在本xml中,需要在前面加上namespace;
column:订单信息中关联用户信息查询的列,是user_id;
关联查询的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.itcast.mybatis.po.User"
select="findUserById" column="user_id">
</association>
</resultMap>
<!-- (1)一对一: 查询订单关联查询用户信息:使用resultType -->
<select id="findOrdersUser" resultType="cn.itcast.mybatis.po.OrdersCustom">
SELECT ORDERS.*, USER.username, USER.sex, USER.address FROM ORDERS,USER
WHERE orders.user_id=user.id
</select>
<!-- (2)一对一: 查询订单关联查询用户信息,使用resultMap -->
<select id="findOrdersUserResultMap" resultMap="OrderUserResultMap">
SELECT ORDERS.*, USER.username, USER.sex, USER.address FROM ORDERS,USER
WHERE orders.user_id=user.id
</select>
<!-- (3)一对多: 查询订单关联查询用户及订单明细,使用resultMap -->
<select id="findOrdersAndOrderDetailResultMap" resultMap="OrdersAndOrderDetailResultMap">
SELECT ORDERS.*, USER.username, USER.sex, USER.address,
orderdetail.id orderdetail_id, <!-- orderdetail_id 是 id 的别名 -->
orderdetail.items_id, orderdetail.orders_id, orderdetail.items_num
FROM ORDERS, USER, orderdetail
WHERE orders.user_id=user.id AND orderdetail.orders_id=orders.id
</select>
<!-- (4)多对多: 查询用户及用户够买的商品信息,使用resultMap -->
<select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap">
SELECT ORDERS.*, USER.username, USER.sex, USER.address,
orderdetail.id orderdetail_id, <!-- orderdetail_id 是 id 的别名 -->
orderdetail.items_id, orderdetail.orders_id, orderdetail.items_num,
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
</select>
<!-- (5)延迟加载:查询订单关联查询用户信息,用户信息需要延迟加载 -->
<select id="findOrdersUserLazyLoadingResultMap" resultMap="OrdersUserLazyLoadingResultMap">
SELECT * FROM orders
</select>
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE id=#{id}
</select>
</mapper>
~ 接口类:OrdersMapperCustom.java
public interface OrdersMapperCustom {
// (1)一对一: 查询订单关联查询用户信息:使用resultType
public List<OrdersCustom> findOrdersUser() throws Exception;
// (2)一对一: 查询订单关联查询用户信息,使用resultMap
public List<Orders> findOrdersUserResultMap() throws Exception;
// (3)一对多: 查询订单关联查询用户及订单明细,使用resultMap
public List<Orderdetail> findOrdersAndOrderDetailResultMap() throws Exception;
// (4)多对多: 查询用户及用户够买的商品信息,使用resultMap
public List<User> findUserAndItemsResultMap() throws Exception;
// (5)延迟加载:查询订单关联查询用户信息,用户信息需要延迟加载
public List<Orders> findOrdersUserLazyLoadingResultMap() throws Exception;
}
~ pojo类:User、Orders、OrdersCustom、Orderdetail、Items
// 用户的基类 User.java
public class User {
private int id; // 用户id
private String username; // 用户姓名
private String sex; // 用户性别
private Date birthday; // 用户生日
private String address; // 用户地址
private List<Orders> orders; // 订单信息列表
}
// 订单的基类 Orders.java
public class Orders {
private Integer id;
private Integer userId; // 下单用户id
private String number; // 订单号
private Date createtime; // 创建订单时间
private String note; // 备注
private User user; // 用户信息
private List<Orderdetail> orderdetails; // 订单明细列表
}
// 订单的扩展类 OrdersCustom.java
// 通过此类映射订单和用户查询的结果,让此类继承User和Orders两个类中包含查询出来的字段较多的类;
public class OrdersCustom extends Orders {
// 添加用户属性信息:user.username、user.sex、user.address
private String username;
private String sex;
private String address;
}
// 订单明细类 Orderdetail.java
public class Orderdetail {
private Integer id;
private Integer orderId; // 订单id
private Integer itemsId; // 商品id
private Integer itemsNum; // 商品购买数量
private Items items; // 商品信息列表
}
// 商品信息类 Items.java
public class Items {
private Integer id;
private String name; // 商品名称
private Float price; // 商品定价
private String pic; // 商品图片
private Date creametime; // 生产日期
private String detail; // 商品描述
}
~ 测试文件:OrdersMapperCustomTest.java
public class OrdersMapperCustomTest {
private SqlSessionFactory sqlSessionFactory;
@Before // 此方法是在执行测试之前执行
public void setUp() throws Exception {
// 创建SqlSessionFactory
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test // (1)一对一: 查询订单关联查询用户信息:使用resultType
public void testFindOrdersUser() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
List<OrdersCustom> list = ordersMapperCustom.findOrdersUser();
System.out.println(list);
sqlSession.close();
}
@Test // (2)一对一: 查询订单关联查询用户信息,使用resultMap
public void testFindOrdersUserResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
List<Orders> list = ordersMapperCustom.findOrdersUserResultMap();
System.out.println(list);
sqlSession.close();
}
@Test // (3)一对多: 查询订单关联查询用户及订单明细,使用resultMap
public void testfindOrdersAndOrderDetailResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
List<Orders> list = ordersMapperCustom.findOrdersAndOrderDetailResultMap();
System.out.println(list);
sqlSession.close();
}
@Test // (4)多对多: 查询用户及用户够买的商品信息,使用resultMap
public void testfindUserAndItemsResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
List<User> list = ordersMapperCustom.findUserAndItemsResultMap();
System.out.println(list);
sqlSession.close();
}
@Test // (5)延迟加载:查询订单关联查询用户信息,用户信息需要延迟加载
public void testFindOrdersUserLazyLoadingResultMap() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class);
// 查询订单信息(单表)
List<Orders> list = ordersMapperCustom.findOrdersUserLazyLoadingResultMap();
for(Orders orders:list){ // 遍历上边的订单列表
User user = orders.getUser(); // 执行getUser()方法去查询用户信息,这里实现按需加载;
System.out.println(user);
}
sqlSession.close();
}
}
>> 高级映射 - 一对一查询
需求:查询订单信息,关联查询创建订单的用户信息;
~ sql语句
- 确定查询的主表:订单表
- 确定查询的关联表:用户表
- 查询使用内连接还是外联接:由于order表中有一个外键(user_id),通过外键关联查询用户表,只能查询出一条记录,所以可以使用内连接;
SELECT ORDERS.*,USER.username,USER.sex,USER.address
FROM ORDERS,USER WHERE orders.user_id=user.id
~ resultType实现
- (1)创建pojo:User、Orders、OrdersCustom
将上边的SQL的查询结果映射到pojo类中,pojo中必须包括所有查询的列名;
原始的Orders.java
不能映射查询出来的全部字段,所以需要创建Orders原始类的扩展类OrdersCustom.java
类;
扩展类继承User和Orders两个类中包含查询出来的字段较多的类;
- (2)映射文件:
OrdersMapperCustom.xml
- (3)接口类:
OrdersMapperCustom.java
- (4)测试文件:
OrdersMapperCustomTest.java
~ resultMap实现
- (1)使用resultMap映射的思路:
使用resultMap将查询结果中的订单信息映射到Orders对象中,在Orders类中添加User属性,将关联查询出来的用户信息映射到Orders对象中的user属性中; - (2)在Orders类中添加User属性:
- (3)映射文件:
OrdersMapperCustom.xml
(4)接口类:OrdersMapperCustom.java
(5)测试文件:OrdersMapperCustomTest.java
~ resultType和resultMap实现一对一查询小结
- resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射;如果没有查询结果的特殊要求,建议使用resultType;
- resultMap:需要在映射文件中单独定义resultMap,实现有点麻烦,如果对对查询结果又特殊的要求,使用resultMap可以完成将关联查询映射到pojo属性中;【特殊要求:将查询出来的信息映射到定义在pojo里面的pojo的属性中】
- resultMap可以实现延迟加载,resultType无法实现延迟加载;
>> 高级映射 - 一对多查询
- 需求:查询订单及订单明细的信息;
- sql语句
- 确定主查询表:订单表 Orders;
- 确定关联查询表:订单明细表 Orderdetail;
- 在一对一查询基础上添加订单明细表关联即可;
- 分析
- 使用resultType将上边的查询结果映射到pojo中,订单信息就是重复;
- 要求:对Orders映射不能出现重复记录;
在Orders.java
类中添加List<OrderDetail> orderDetails;
属性;最终会将订单信息映射到Orders对象中,订单所对应的订单明细映射到Orders对象中的orderDetail属性中;
映射成的Orders记录数为两条(Orders信息不重复);每个Orders中的orderDetails的属性存储了该订单所对应的订单明细;
- 使用resultType将上边的查询结果映射到pojo中,订单信息就是重复;
- 在Orders类中添加list订单明细属性
- 映射文件:
OrdersMapperCustom.xml
- 接口类:
OrdersMapperCustom.java
- 测试文件:
OrdersMapperCustomTest.java
- 小结:
- mybatis使用resultMap的collection标签对关联查询的多条记录映射到一个list集合属性中;
- 使用resultType实现:将订单明细映射到Orders类中的orderdetails属性中,需要自己处理,使用双循环遍历,去掉重复记录,将订单明细放在orderdetails中;
>> 高级映射 - 多对多查询
- 需求:查询用户及用户购买的商品信息
- sql语句
- 查询主表:用户表User;
- 关联表:由于用户和商品没有直接关联,通过订单和订单明细表进行关联,所以关联表是:Orders、Orderdetail、Items;
- 映射思路
- 将用户信息映射到User中;
- 在User类中添加订单列表属性
List<Orders> orders;
,将用户创建的订单信息映射到orders列表中; - 在Orders类中添加订单明细列表属性
List<Orderdetail> orderdetails;
,将订单明细映射到orderdetails列表中; - 在Orderdetail类中添加商品信息Items属性
List<Items> items;
,将订单明细所对应的商品信息映射到items列表中;
- 在pojo中添加属性:
- 映射文件:
OrdersMapperCustom.xml
- 接口类:
OrdersMapperCustom.java
- 测试文件:
OrdersMapperCustomTest.java
- 多对多查询总结:
- 将查询用户购买的商品明细清单,(用户名、用户地址、购买商品名称、购买商品数量);
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能; - 一对多是多对多的特例,如下需求:查询用户购买的商品信息,用户和商品的关系是多对多关系;
- 需求1:
查询字段:用户账户、用户名称、用户性别、商品名称、商品价格(最常见);
企业开发中常见明细列表,用户购买商品明细列表,使用resultType将上边查询列映射到pojo输出; - 需求2:
查询字段:用户账户、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中; - 总结:使用resultMap是针对那些对查询结果有特殊要求的功能,Eg:特殊要求映射成list中包含多个list;
- 将查询用户购买的商品明细清单,(用户名、用户地址、购买商品名称、购买商品数量);
>> 高级映射 - resultType、resultMap 总结
~ resultType
- 作用:将查询结果按照sql列名pojo属性名一致映射到pojo中;
- 场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可;
~ resultMap
使用association和collection完成一对一和一对多的高级映射;
- association:
- 作用:将关联查询信息映射到一个pojo对象中;
- 场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息;
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap;
- collection:
- 作用:将关联查询信息映射到一个list集合中;
- 场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样做的目的是方便对查询结果集进行遍历查询;
如果使用resultType无法将查询结果映射到list集合中;
>> 高级映射 - 延迟加载
~ 什么是延迟加载
resultMap可以实现高级映射(使用association、collection实现一对一和一对多映射),association、collection具备延迟加载的功能;
需求:查询订单并且关联查询用户信息,使用association把用户信息映射到订单对象中的user属性中,当我们需要查询用户信息时再查询用户信息,把对用户信息的按需去查询就是延迟加载;
延迟加载:先从单表查询,需要时再从关联表去关联查询,大大提高数据库的性能,因为查询单表要比关联查询多张表速度要快;
~ 使用association实现延迟加载
- (1)需求:查询订单并且关联查询用户信息;
- (2)思路:需要定义两个mapper的方法对应的statement:
- 1、先只查询订单信息:在查询订单的statement中使用association去延迟加载(执行)下面的statement(关联查询用户信息);
- 2、再使用子查询,关联查询用户信息:通过上面查询到的订单信息中的user_id 去关联查询用户信息;
- 3、上边先去执行findOrdersUserLazyLoading,当需要去查询用户信息的时候,再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来;
- 1、先只查询订单信息:在查询订单的statement中使用association去延迟加载(执行)下面的statement(关联查询用户信息);
- (3)映射文件:
OrdersMapperCustom.xml
:
- (4)在主配置文件中进行延迟加载配置:
- (5)接口类:
OrdersMapperCustom.java
- (6)测试文件:
OrdersMapperCustomTest.java
测试思路:
- 1、执行上边mapper方法(findOrdersUserLazyLoadingResultMap),内部去调用
namespace="cn.itcast.mybatis.mapper.OrdersMapperCustom"
中的findOrdersUserLazyLoadingResultMap 只查询orders信息(单表); - 2、在程序中遍历上一步查询出的list,当调用Orders中的getUser方法时,开始进行延迟加载;
- 3、延迟加载,去调用findUserById这个方法 获取用户信息;
- 1、执行上边mapper方法(findOrdersUserLazyLoadingResultMap),内部去调用
~ 不使用mybatis,实现延迟加载功能
不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载?
实现方法:定义两个mapper方法:1、查询订单列表,2、根据用户id查询用户信息;
实现思路:先查询第一个mapper方法,获取订单信息列表,在程序中(service),按需去调用第二个mapper方法去查询用户信息;
总之:使用延迟加载方法,先去查询简单的sql(最好是单表,也可以是关联查询),再去按需要加载关联查询的其他信息;
>> 查询缓存
- 什么是查询缓存:mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能;
- 缓存分类:缓存有一级缓存、二级缓存;
- 一级缓存:是SqlSession级别的缓存;在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据;不同的sqlSession之间的缓存数据区域(HashMap)是互不影响的;
- 二级缓存:是Mapper级别的缓存;多个sqlSession去操作同一个mapper的SQL语句,多个sqlSession可以共用二级缓存,二级缓存是跨sqlSession的;
- 使用缓存优势:如果缓存中有数据,就不用从数据库中获取,大大提高系统性能;
>> 查询缓存 - 一级缓存
~ (1)一级缓存工作原理
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,则从数据库中查询用户信息;得到用户信息之后将其存储到一级缓存中;
如果sqlSession去执行commit操作(执行插入、更新、删除操作),会清空sqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是最新的信息,避免脏读;
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,则直接从缓存中获取用户信息;
~ (2)一级缓存测试
mybatis默认支持一级缓存,不需要在配置文件中去配置;
~ (3)一级缓存应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中;一个service方法中包括很多mapper方法调用;
如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空;
>> 查询缓存 - 二级缓存
~(1)二级缓存工作原理
1、首先开启mybatis的二级缓存;
2、sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中;
3、如果sqlSession3去执行相同的mapper下SQL,执行commit提交,会清空该mapper下的二级缓存区域的数据;
4、sqlSession2去查询用户id为1的用户信息,会去缓存中找是否存在数据,如果存在,直接从缓存中取出数据;
~ (2)二级缓存与一级缓存的区别
1、二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域;
2、UserMapper有一个二级缓存区域(按namespace分),其他mapper也有自己的二级缓存区域(按namespace分);
3、每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将会存在相同的二级缓存区域中;
~ (3)开启二级缓存
mybatis的二级缓存是mapper范围级别的,除了在SQLMapConfig.xml
中设置二级缓存的总开关外,还要在具体的mapper.xml
中开启二级缓存;
Cache Hit Ratio
控制台中出现这句话说明二级缓存开启成功;
~ (4)调用pojo类实现序列化接口
是为了将缓存数据取出执行反序列化操作,因为二级缓存数据的存储介质多种多样,不一定在内存中;
public class User implements Serializable{...}
~ (5)二级缓存测试
~ (6)mapper中useCache配置
在statement中设置useCache=false
,可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询;
默认情况是true,即该sql使用二级缓存;<select id="findUserById" resultType="user" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=”false”,禁用二级缓存;
~ (7)刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其他insert、update、delete操作数据后,需要刷新缓存,如果不执行刷新缓存就会出现脏读;
设置statement配置中的flushCache="true"
属性,默认情况下为true,即刷新缓存; 如果改成false则不会刷新缓存(即使执行commit操作,也不会清空缓存),使用缓存时如果手动修改数据库中的查询数据,就会出现脏读;
<select id="findUserById" resultType="user" flushCache="true">
总结:一般执行完commit操作都需要刷新缓存,flushCache=”true”表示刷新缓存,这样可以避免脏读;
~ (8)mybatis整合ehcache
ehcache是一个分布式缓存框架;其他还有redis、memcached;
1、分布缓存:
我们的系统为了提高系统并发、性能等,一般对系统进行分布式部署(集群部署方式);不使用分布缓存,缓存的数据在各个服务器单独存储,不方便系统开发,所以要使用分布式缓存对缓存数据进行集中管理; mybatis只是实现数据的简单查询、存储,无法实现分布式缓存,需要和其他分布式缓存框架进行整合;
2、整合方法:
- Mybatis默认实现的一个cache实现类;
- mybatis提供了一个cache接口,若要实现自己的缓存逻辑,实现cache接口即可;mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类;
- 加入ehcache包:
- 配置mapper中的cache标签中的type属性:
- 加入ehcache.xml配置文件:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3、二级缓存应用场景:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等;
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定;
4、二级缓存局限性:
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空;解决此类问题需要在业务层根据需求对数据有针对性缓存;
>> Mybatis 和 Spring 整合
~ 整合思路
1、需要spring通过单例方式管理SqlSessionFactory;
2、spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession(spring和mybatis整合自动完成);
3、持久层的mapper都需要有spring进行整理;
~ 整合环境 - 项目结构
创建一个新的java工程,导入mybatis的jar包、spring的jar包、mybatis和spring的整合包、数据库驱动包,数据库连接池的jar包;
~ applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 加载数据源:使用dbcp; class属性的值在dbcpjar包的org.apache.commons.dbcp2包中 -->
<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}" />
<!-- <property name="maxActive" value="10" /> 貌似dbcp2弃用这个属性了 -->
<property name="maxIdle" value="5" />
</bean>
<!-- SqlSessionFactory:类在mybatis-spring整合包中 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载mybatis的配置文件 -->
<property name="configLocation" value="mybatis/SqlMapConfig.xml"></property>
<!-- 加载数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 原始dqo接口 -->
<bean id="userDao" class="cn.itcast.ssm.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<!-- mapper配置; MapperFactoryBean:根据mapper接口生成代理对象 -->
<!-- <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="cn.itcast.ssm.mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean> -->
<!-- mapper批量扫描:从mapper包中扫描出mapper接口,自动创建代理对象并在spring容器中注册;
遵循规范:将mapper.java和mapper.xml映射文件名称保持一致,并且在一个目录中;
自动扫描出来的mapper的bean的id为mapper类名(首字母小写)
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包名;如果扫描多个包,不能使用*,每个包中间使用逗号隔开; -->
<property name="basePackage" value="cn.itcast.ssm.mapper"></property>
<!-- sqlSessionFactoryBeanName如果使用sqlSessionFactory, 则加载数据库属性的配置文件失效,因为这个先执行 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
</beans>
~ 主配置文件 SqlMapConfig.xml
<configuration>
<typeAliases>
<package name="cn.itcast.ssm.mapper" />
</typeAliases>
<mappers> <!-- 加载映射文件:和spring整合后,使用mapper扫描器,这个就不需要再设置了; -->
<mapper resource="sqlmap/User.xml" />
<package name="cn.itcast.mybatis.mapper" />
</mappers>
</configuration>
~ 测试文件 UserMapperTest
public class UserMapperTest {
private ApplicationContext applicationContext;
@Before // 在setUp这个初始化方法里得到spring容器
public void setUp() throws Exception {
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserByIdResultMap() throws Exception {
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
User user = userMapper.findUserById(2);
System.out.println(user);
}
}
~ 配置 SqlSessionFactory
在applicationContext.xml
文件中配置SqlSessionFactory和数据源,SqlSessionFactory在mybatis和spring的整合包下;
~ 原始dao开发(和Spring整合后)
(1)User.xml
(2)SqlMapConfig.xml
文件中加载User.xml
文件
(3)UserDao接口类:
(4)UserDaoImpl实现类:
UserDaoImpl接口实现类需要注入SqlSessionFactory,通过spring进行注入;这里spring声明配置方式,配置dao的bean:
让UserDaoImpl实现类继承SqlSessionDaoSupport;
(5)在applicationContext.xml
文件中配置dao的bean:
(6)测试文件:
~ mapper 开发(和Spring整合后)
(1)UserMapper.java文件:
(2)UserMapper.xml文件:
(3)在applicationContext.xml
文件中通过MapperFactoryBean创建代理对象:
(4)此方法存在的问题:需要针对每个mapper进行配置,麻烦;
(5)解决问题:通过MapperScannerConfigurer进行mapper扫描(推荐使用)
(6)测试文件:
>> Mybatis 逆向工程
官方文档:http://www.mybatis.org/mybatis-3/
jar包 GitHub下载地址:https://github.com/mybatis/generator/releases
下载逆向工程jar包:mybatis-generator-core-1.3.7
~ 什么是逆向工程
mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程(是一个工具),可以针对单表自动生成mybatis执行所需要的代码(mapper.java
、mapper.xml
、pojo…)
企业实际开发中,常用的逆向工程方式:由数据库的表生成java代码;
~ 使用逆向工程
运行逆向工程:通过Java program、通过eclipse的插件生成代码;建议使用java程序的方式,不依赖开发工具;
(1)新建一个java工程:新建一个java工程专门用来使用逆向工程生成代码,再将生成的代码拷贝到自己的项目中;直接在自己的项目中生成,会有风险:因为mybatis是根据配置文件来生成的,如果生成的路径中有相同的文件,那么就会覆盖原来的文件;
(2) 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>
<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="123456">
</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">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置,重要!! -->
<javaClientGenerator type="XMLMAPPER" targetPackage="cn.itcast.ssm.mapper" targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表,要生成哪些表,就写哪些表,要和数据库中对应,不能写错! -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
</context>
</generatorConfiguration>
上面的配置文件中重点关注:
- 连接数据库;
- 指定要生成代码的位置,要生成的代码包括po类,
mapper.xml
和mapper.java
; - 指定数据库中想要生成哪些表;
(3)执行生成程序:
public class GeneratorSQLmap {
public void generator() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
// 指向逆向工程配置文件
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSQLmap generatorSqlmap = new GeneratorSQLmap();
generatorSqlmap.generator();
} catch (Exception e) { e.printStackTrace();}
}
}
(4)测试生成的代码:
public class ItemsMapperTest {
private ApplicationContext applicationContext;
private ItemsMapper itemsMapper;
@Before // 在setUp这个初始化方法里得到spring容器
public void setUp() throws Exception {
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
itemsMapper = (ItemsMapper) applicationContext.getBean("itemsMapper");
}
@Test // 插入
public void testInsert() {
Items item = new Items();
item.setName("手机");
item.setPrice(123f);
itemsMapper.insert(item);
}
@Test // 根据主键查询
public void testSelectByPrimaryKey() {
Items items = itemsMapper.selectByPrimaryKey(2);
System.out.println(items);
}
@Test // 自动以条件查询
public void testSelectByExample() {
ItemsExample itemsExample = new ItemsExample();
// 通过criteria构造查询条件
ItemsExample.Criteria criteria = itemsExample.createCriteria();
criteria.andNameEqualTo("商品名称1");
// 可能返回多条数据
List<Items> list = itemsMapper.selectByExample(itemsExample);
System.out.println(list);
}
@Test // 更新数据
public void testUpdateByPrimaryKey() {
// 对所有字段进行更新,需要先查询出来再更新
Items item = itemsMapper.selectByPrimaryKey(1);
item.setName("电话");
itemsMapper.updateByPrimaryKey(item);
// 如果传入字段不为空,才更新;在批量更新中使用此方法,不需要先查询再更新;
// itemsMapper.updateByPrimaryKeySelective(record)
}
}