Mybatis 复习
概述
mybatis 是一个用java编写的持久层框架, 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程,它使用了ORM思想实现了结果 集的封装
ORM Object Relational Mapping 对象关系映射,把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
入门案例
创建数据库,创建User表
创建maven工程并导入坐标
<project ...>
...
<packaging>jar</packaging> <dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
</dependencies>
</project>
编写实体类
public class User{
private Integer id;
private String username;
private String password;
getter()...
setter()...
toString()...
}
创建UserDao接口
public interface UserDao{
List<User> findAll(); //查询所有用户
}
创建主配置文件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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的四个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments> <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="com/whteway/dao/UserDao.xml"/>
</mappers>
</configuration>
创建映射配置文件UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.whteway.dao.UserDao">
<!-- 配置查询所有,id为对应方法名,resultType指定结果封装位置 -->
<select id="findAll" resultType="com.whteway.domain.User">
select * from user
</select>
</mapper>
- 注意
- Mybatis中把持久层的操作接口名称和映射文件也叫做Mapper,所以dao也可以叫mapper
- 在idea中创建com.whteway.dao包时是三级结构,创建目录时是一级目录
- 映射配置文件的位置结构必须和dao接口的包结构相同
- 映射配置文件的mapper标签的namespace属性的取值必须是dao接口的全限定类名
- 映射配置文件的操作标签的id属性必须是对应的方法名
- 遵循以上规则,则不用写dao实现类
使用
public void test(){
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user: users)
System.out.println(user);
//6.释放资源
session.close();
in.close();
}
注解方式
主配置文件SqlMapConfig.xml
...
<mappers>
<!-- <mapper resource="com/whteway/dao/UserDao.xml"/> -->
<mapper class="com.whteway/dao/UserDao"/>
</mappers>
...
UserDao.java
public interface UserDao{
@Select("select * from uesr")
List<User> findAll();
}
删除UserDao.xml
连接池
mybatis连接池提供三种方式的配置
配置位置:主配置文件中dataSource标签的type属性
type取值:
POOLED
采用传统的javax.sql.DataSource规范中的连接池
UNPOOLED
采用传统的获取连接的方式,实现了DataSource接口,没有用连接池思想
JNDI
采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器能拿到的DataSource不一样,如果不是web或者maven的war工程,就不能使用
事务管理
- 通过SqlSession的 commit() 和 rollback() 方法
- SqlSession openSession(boolean autoCommit); 传入false开启事务
自定义dao实现类(不常用, 用来研究原理)
public class UserDaoImpl implements UserDao{
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
public List<User> findAll(){
//使用工厂创建SqlSession对象
SqlSession session = factory.openSession();
//使用session对象执行sql语句
List<User> users = session.selectList("映射配置文件的namespace.findAll");
session.close();
//返回查询结果
return users;
}
}
//-----------------使用----------------
public void test(){
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in); UserDao userdao = new UserDaoImpl(factory);
List<User> users = userDao.findAll(); in.close();
}
流程分析
使用过程
读配置文件
读取配置文件时用绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法
- 使用类加载器,它只能读取类路径的配置文件
- 使用SerbletContext对象的getRealPath()
创建SqlSessionFactory工厂
建造者模式(Builder Pattern)
使用工厂生产SqlSession对象
工厂模式(Factory Pattern)
使用SqlSession创建Dao接口的代理对象
代理模式(Proxy Pattern)
使用代理对象执行方法
释放资源
自定义dao中selectList()方法的执行流程,也是代理对象增强的逻辑
- 注册驱动,获取Connection对象(需要数据库信息)
- 通过SqlMapConfig.xml的数据库信息,解析xml文件用到的是dom4j技术
- 获取预处理对象PreparedStatement(需要sql语句)
- 通过SqlMapConfig.xml中的mapper标签定位UserDao.xml,映射配置文件中有sql语句
- 执行查询,得到结果集ResultSet
- 遍历结果集用于封装
- 根据UserDao.xml中的resultType反射获得User对象,User对象的属性名和表中列名一致,可以一一封装进user对象中,再把user对象封装进list中
- 所以,要想让selectList()执行,需要提供两个信息,连接信息和映射信息,映射信息包括sql语句和封装结果的实体类的全限定类名,所以映射信息可以用map存储
创建代理对象流程
根据dao接口的字节码创建dao的代理对象
public <T> T getMapper(Class<T> daoInterfaceClass){
/*
类加载器,使用和被代理对象相同的类加载器
接口数组,和被代理对象实现相同的接口
增强逻辑,自己提供,此处是一个InvocationHandler接口,需要写一个该接口的实现类,在其中调用selectList()方法
*/
Proxy.newProxyInstance(类加载器, 接口数组,增强逻辑);
}
单表CRUD
DML操作后要调用SqlSession.commit()方法进行事务提交
需要指定参数类型(实体类的全限定类名,基本类型可以用int,Integer,java.lang.Integer)
SQL语句传参时需要用#{实体类的属性值},此处的属性值是setter方法的set后的字符串并首字母小写
保存 UserDao.saveUser(User user);
xml
<insert id="saveUser" parameterType="com.whteway.domain.User">
insert into user(username, address, sex, birthday) values(#{id},#{username},#{password})
</insert>
更新 UserDao.updateUser(User user);
xml
<update id="updateUser" parameterType="com.whteway.domain.User">
update user set username=#{username}, password#{password}) where id=#{id}
</insert>
注解
删除 UserDao.deleteUser(Integer userId);
xml
<delete id="deleteUser" parameterType="Integer">
update from user where id=#{uid}
</delete>
查询单个 UserDao.findById(Integer id);
xml
<select id="findById" parameterType="INT" resultType="com.whteway.domain.User">
select * from user where id=#{id}
</select>
查询多个 UserDao.findByName(String name);
xml,传参的时候需要加%%(推荐,底层用的是PerparedStatement执行语句),不加%%可以使用固定写法 '%${value}%'(底层用的是Statement执行语句)
<select id="findByName" parameterType="string" resultType="com.whteway.domain.User">
select * from user where username like #{name}
/* select * from user where username like '%${value}' */
</select>
聚合函数 UserDao.findTotal();
xml
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
扩展
OGNL表达式
Object Graphic Navigation Language 对象 图 导航 语言
通过对象的取值方法来获取数据,省略get
如:user.getUsername(); --> user.username
mybatis中可以直接写username而不写user.,因为在parameterType中已经指定了类
根据自定义的条件类查询 UserDao.findByName(QueryVo vo); 设QueryVo中有属性User
xml
<select id="findByName" parameterType="com.whteway.domain.QueryVo" resultType="com.whteway.domain.User">
select * from user where username like #{user.username}
</select>
获取保存数据的id
sql中有一条语句"select last_insert_id();"可以返回上一条保存的记录的id,使用下面的配置后mybatis会将查出来的id值存入作为参数的user对象中
<insert ...>
<selectKey keyProperty="id" keyColum="id" resultType="int" order="AFTER">
select last_insertid()
</selectKey>
insert ...
</insert>
返回值与列名的对应
返回值可以是基本类型,也可以是pojo(Plain Ordinary Java Object,普通java对象,即javaBeans)
当实体类属性和查询结果列名不一致时会导致数据无法封装进resultType中
可以在sql语句中通过起别名的方式解决
在映射配置文件中配置查询结果的列名和实体类属性名的对应关系,设User中有uid,name,pwd属性
<!-- 定义 -->
<resultMap id="userMap" type="com.whteway.domain.User">
<id property="uid" column="id"></id>
<result property="name" column="username"></result>
<result property="pwd" column="password"></result>
</resultMap> <!-- 使用,替换resultType -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
自定义dao实现类完成CRUD
用session的不同方法执行sql语句,传入映射配置文件的namespace.SQL语句的id和sql需要的参数
selectOne,selectList,insert,update
public class UserDaoImpl implements UserDao{
//只有第二步需要变化
@Override
public void saveUser(User user){
//1.使用工厂创建SqlSession对象
SqlSession session = factory.openSession();
//2.使用session对象执行sql语句
session.insert("com.whteway.dao.UserDao.saveUser", user);
//3.释放资源
session.close();
//4.返回查询结果
return;
}
}
properties标签
配置properties,可以用标签配,也可以引用外部配置文件信息
resource属性,用于指定配置文件的位置,按照类路径的写法来写,并且必须存在于类路径下(放在和SqlMapConfig.xml相同路径下)
url属性,按照url的写法来写地址:协议://主机:端口 URI
<configuration>
<properties resource="jdbcConfig.properties">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<!-- 在实际配置中用"${driver}"取值 -->
</configuration>
typeAliases标签
配置别名,只能配置实体类的别名,当指定了别名就不再区分大小写
mybatis为基本类型和String配置了别名
<typeAliases>
<typeAlias type="com.whteway.domain.User" alias="user"></typeAlias>
</typeAliases>
package标签
在typeAliases中使用:为整个包中的类配置别名,当指定之后,该包下的实体类都会以类名注册别名
在mappers中使用:指定dao接口所在的包,指定后,不需要再写resource或class了
<typeAliases>
<package name="com.whteway.domain"></package>
</typeAliases>
<mappers>
<package name="com.whteway.dao"></package>
</mappers>
动态SQL语句
if标签:满足条件则将标签内的语句拼接到sql语句后
<!-- 根据条件查询,传入的条件可能是id,也可能是username -->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from where 1=1
<if test="username != null">and username=#{username}</if>
<if test="id != null">and id=#{id}</if>
</select>
where标签:sql后加 where 再将标签内的语句拼接到sql后
<!-- if标签代码改进 -->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from
<where>
<if test="username != null">and username=#{username}</if>
<if test="id != null">and id=#{id}</if>
</where>
</select>
foreach标签
循环将标签内部的语句和属性中的片段拼接到sql后
open属性:开始片段;close属性:结束片段;separator属性:每个元素的分割符
<!-- 根据queryvo中提供的id集合,查询用户信息 -->
<select id="findByIds" resultMap="userMap" parameterType="queryvo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
sql标签
抽取重复的sql语句
<!-- 定义,末尾不要加分号 -->
<sql id="defaultUser">select * from user</sql>
<!-- 使用 -->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
<include refid="defaultUser"></include>
where id=#{id}
</select>
多表查询
关键在于SQL语句的编写,在实体类和映射配置中只能定义查询结果的封装
定义映射关系时,column属性的值是对应查询结果的列名的,如果有重复字段名,则需要在sql语句中使用别名,那么在resultMap中的column属性值也要与别名对应
一对多(Mybatis中把多对一当作一对一处理)
user(id, username, password),account(id, uid, money);
User.java
public class User implements Serili{
/* ... */
//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account> accounts;
/* getters and setters */
}
<resultMap id="userAccountMap" type="user">
<!-- 普通属性的关系映射... -->
<!-- 一对多的关系映射,查询时user.account.id需要取别名 -->
<collection property="accounts" ofType="account">
<id column="aid" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
</collection>
</resultMap>
Acount.java
public class Account implements Serializable{
/* ... */
//一对一关系映射,从表实体应该包含主表实体的对象引用
private User user;
/* getters and setters */
}
<resultMap id="accountUserMap" type="account">
<!-- 普通属性的关系映射... -->
<!-- 一对一的关系映射 -->
<association property="user" column="uid" javaType="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
</association>
</resultMap>
多对多,需要使用中间表
user(id, username, password),role(id, name),user_role(uid, rid);
在User类中加Role的集合roles,并在UserDao.xml中配置resultMap,使用collection标签配置roles
在Role类中加User的集合users,并在RoleDao.xml中配置resultMap,使用collection标签配置users
延迟加载
问题:一个用户有多个账户,在查询用户时,即使用了多表查询的sql语句,也可能用不到关联的帐户信息,这时候查询账户表浪费资源
延迟加载(按需加载,懒加载)
在真正使用数据时才发起查询,不用的时候不查询
比如:查询用户和账户时,输入的语句是,实际运行时,由于调用查询方法一定会有User对象或列表接收返回值,所以会立即"select * from user",当用到user.accounts时,才会执行"select * from account where uid=?"
立即加载
只要调用方法,马上发起查询
方式选择
一对多、多对多:通常采用延迟加载,即有查询结果带集合时延迟
多对一、一对一:通常采用立即加载
配置开启延迟加载
<!-- SqlMapConfig.xml -->
<configuration>
<settings>
<!-- 打开支持延迟加载开关 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 将积极(立即)加载改为消息(延迟)加载,3.4.2及之后默认为false,可以不配 -->
<Setting name="aggressiveLazyLoading" value="false" />
</settings>
</configuration>
缓存
缓存:存在于内存中的数据
缓存可以减少和数据库的交互次数,提高执行效率
适用情况
适用:经常查询且不常改变的,数据的正确性对最终结果影响不大的
不适用:经常改变的,正确性对最终结果影响很大的,如汇率,股价等
Mybatis一级缓存
- 指SqlSession对象的缓存,当SqlSession对象消失时,缓存随之消失
- 当执行查询的结果会同时存入到SqlSession提供的一块区域中,该区域结构是一个Map,当查询时,mybatis会先去sqlsession中查询是否有对应数据,如果有则直接用
- Mybatis默认开启一级缓存
- 缓存Map的value是对象,所以两次查询得到的对象是同一个
- sqlSession.clearCache(); //不销毁sqlSession清空缓存
- 当执行DML时,会清空一级缓存
Mybatis二级缓存
指SqlSessionFactory对象的缓存
配置开启二级缓存(三步)
<!-- SqlMapConfig.xml -->
<configuration>
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
</configuration>
<!-- UserDao.xml -->
<mapper ...>
<cache/>
<select ... useCache="true"> ... </select>
</mapper>
二级缓存中存入的是数据,而不是对象,所以两次查询得到的对象不是同一个
注解开发
SqlMapConfig.xml 主配置文件不能省略
注意:只要使用注解开发,如果同时存在映射配置文件,即使mappper中指定了class属性,也会报错
<configuration>
<!-- ... -->
<!-- 指定带有注解的dao接口位置 -->
<mappers>
<package name="com.whteway.dao"></package>
</mappers>
</configuration>
CRUD注解,加在dao接口的方法上
- @Select("sql") 相当于select标签
- @Insert("sql") 相当于insert标签
- @Update("sql") 相当于update标签
- @Delete("sql") 相当于delete标签
映射关系注解
@Results 相当于resultMap标签
@Result 相当于result标签
@ResultMap 相当于CRUD标签的resultMap属性
使用方式,@Results可以直接用在方法上(不用指定id属性)
指定id属性后,其他方法可以用@ResultMap("resultsId")调用对应@Results
@Results(value={
@Result(id="true", column="id", property="userId"),
@Result(column="username", property="name"),
@Result(column="password", property="psw")
})
多表查询的注解
@One 指定一的一方,用在Result标签中
select属性指向一个方法,该方法可以通过User主键查询User,该方法必须存在
fetchType属性指定延迟加载策略,FetchType.DEFAULT(),FetchType.LAZY(),FetchType.EAGER(),
@Select("select * from account")
@Results(value={
/* ... */
@Result(column="uid", property="user", one=@One(select="com.whteway.UserDao.findById", fetchType=FetchType.EAGER))
})
List<Account> findAll();
@Many 指定多的一方,用在Result标签中
select属性指向一个方法,该方法可以通过User主键(Account的外键)查询所有符合条件的Account,该方法必须存在
fetchType同@One
@Select("select * from user")
@Results(value={
/* ... */
@Result(column="id", property="accounts", many=@Many(select="com.whteway.AccountDao.findByUid", fetchType=FetchType.DEFAULT))
})
List<User> findAll();
注解开启二级缓存
@CacheNamespace(blocking = true) 用在Dao接口上
Mybatis 复习的更多相关文章
- mybatis复习01
1.mybatis的历史: mybatis是apache的一个开源项目,2010被google收购,转移到google code. mybatis是一个优秀的持久层框架,对jdbc操作进行了封装,是操 ...
- Mybatis 复习 Mybatis 配置 Mybatis项目结构
pom.xml文件已经贴在了文末.该项目不使用mybatis的mybatis-generator-core,而是手写Entities类,DaoImpl类,CoreMapper类 其中,Entities ...
- MyBatis复习【简单配置CRUD】
这里的案例集成了log4j的日志框架,项目架构: 用到的jar文件 添加配置文件:mybatis-config.xml 和dao层配置文件StudentDao.xml 这里书写了个简单的案例仅为了说 ...
- MyBatis复习
一.对JDBC的总结 1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能. 解决方案:使用数据库连接池管理数据库连接. 2.将sql语句硬 ...
- mybatis 复习笔记03
参考:http://www.mybatis.org/mybatis-3/zh/configuration.html 入门 1. 从 XML 中构建 SqlSessionFactory 每个基于 MyB ...
- mybatis 复习笔记02
1. 一对一查询: 1). 实体类: 2). 定义resultMap <!-- 订单查询关联用户的resultMap 将整个查询的结果映射到cn.itcast.mybatis.po.Orders ...
- mybatis 复习笔记01
本文内容转自传智播客笔记 1. 问题总结 1). 数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能. 设想:使用数据库连接池管理数据库连 ...
- mybatis复习笔记(1):
一.简介:什么是MyBatis 1.MyBatis是一款优秀的持久层框架,支持定制化SQL.存储过程以及高级映射.MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集.MyBatis ...
- SSM
今天内容安排 1:复习mybatis 2:复习springMVC 3:springMVC+spring+mybatis组合起来,搭建一个web应用开发的框架 4:用户管理系统,针对用户的CRUD操作, ...
随机推荐
- 【jsp】案例:显示商品列表 & 问题:List内添加元素,为什么值都变成一样的了
代码: 1.Product: package domain; public class Product { /* `pid` varchar(50) NOT NULL, `pname` varchar ...
- 关于pytest的命令行传参
#conftest.py import pytest def pytest_addoption(parser): #parser:用户命令行参数与ini文件值的解析器 # group = parser ...
- Mybatis-plus中的condition条件
@Test public void testCondition() { String name = "王"; String email = ""; condit ...
- python笔试题
冒泡排序的原理:每次对相邻的两个元素进行比较,若前者大于后者,这将两者的位置交换.第一轮就可以将最大的元素置于列表的最后.几轮循环 冒泡排序的前提条件:有序的列表 import unittest# 冒 ...
- sublime python 配置内容
{"cmd": ["python", "-u", "$file"],"file_regex": &q ...
- 玩转jmeter:beanshell必备技能
beanshell是什么 BeanShell是一个小型嵌入式Java源代码解释器,具有对象脚本语言特性,能够动态地执行标准JAVA语法,并利用在JavaScript和Perl中常见的的松散类型.命令. ...
- WooCommerce代码合集整理
本文整理了一些WooCommerce代码合集,方便查阅和使用,更是为了理清思路,提高自己.以下WooCommerce简称WC,代码放在主题的functions.php中即可. 修改首页和分类页面每页产 ...
- FastDFS 分布式文件系统(部署和运维)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/KamRoseLee/article/det ...
- 2016 ACM/ICPC亚洲区青岛站
A B C D E F G H I J K L M O O O O $\varnothing$ $\varnothing$ $\varnothing$ $\varnothing$ ...
- Sublime Text3 设置
主题:Spacegrey.sublime-theme 配色方案:Mariana 自动保存 参考:https://www.cnblogs.com/mzzz/p/6178341.html "sa ...