目录
mybatis概述
mybatis介绍
mybatis是⼀个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,
⽽不需要花费精⼒去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的⽅式将要执⾏的各种statement配置起来,并通过java对象和statement中sql
的动态参数进⾏映射⽣成最终执⾏的sql语句,最后由mybatis框架执⾏sql并将结果映射为java对象并返
回。
采⽤ORM思想解决了实体和数据库映射的问题,对jdbc进⾏了封装,屏蔽了jdbc api底层访问细节
ORM:Object Relational Mappging 对象关系映射 简单的说: 就是把数据库表和实体类及实体
类的属性对应起来 让我们可以操作实体类就实现操作数据库表。
实体类中的属性和数据库表的字段名称保持⼀致。
JDBC模式
public static void main(String[] args) {
Connection connection;
connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出sql执⾏查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id") + " " + resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
上边使⽤jdbc的原始⽅法(未经封装)实现了查询数据库表记录的操作。
jdbc问题分析
- 数据库链接创建、释放频繁造成系统资源浪费从⽽影响系统性能,如果使⽤数据库链接池可解决此
问题。 - Sql语句在代码中硬编码,造成代码不易维护,实际应⽤sql变化的可能较⼤,sql变动需要改变java
代码。 - 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可
能多也可能少,修改sql还要修改代码,系统不易维护。 - 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数
据库记录封装成pojo对象解析⽐较⽅便。
mybatis原理
基于xml的mybati开发
创建maven工程,导入依赖
<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.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
创建实体类和dao接口
IUserDao
public interface IUserDao {
public List<User> findAll();
}
IUserDao.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.dgut.dao.IUserDao">
<select id="findAll" resultType="com.dgut.domain.User">
select * from user
</select>
</mapper>
创建mybatis主配置文件
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 resource="jdbc.properties">
</properties>
<!-- 配置环境 -->
<environments default="development">
<!-- 配置mysql环境 -->
<environment id="development">
<!-- 配置事务 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源连接池 -->
<dataSource type="POOLED">
<!-- 这⾥⽤得是mysql8的驱动包 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/dgut/dao/iUserDao.xml"/>
</mappers>
</configuration>
测试
UserTest
public class UserTest {
private InputStream inputStream;
private SqlSession session;
private IUserDao userDao;
@Before
public void init() throws IOException {
//1.读取配置⽂件
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory的构建者对象,使⽤构建者创建⼯⼚对象SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.使⽤SqlSessionFactory⽣产SqlSession对象
session = sqlSessionFactory.openSession(true);//true时开启事务自动提交
//4.使⽤SqlSession创建dao接⼝的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
session.close();
inputStream.close();
}
@Test
public void testFindAll(){
List<User> users = userDao.findAll();
for (User user : users){
System.out.println(user);
}
//在实现增删改时⼀定要去控制事务的提交
//session.commit();
}
注意:
1.创建IUserDao.xml 和 IUserDao.java时名称是为了和我们之前的知识保持⼀致。 在
Mybatis中它把持久层的操作接⼝名称和映射⽂件也叫做:Mapper 所以:IUserDao 和
IUserMapper是⼀样的
2. 在idea中创建⽬录的时候,它和包是不⼀样的
包在创建时:com.dgut.dao它是三级结构
⽬录在创建时:com.dgut.dao是⼀级⽬录
3. mybatis的映射配置⽂件位置必须和dao接⼝的包结构相同
4. 映射配置⽂件的mapper标签namespace属性的取值必须是dao接⼝的全限定类名
5. 映射配置⽂件的操作配置(select),id属性的取值必须是dao接⼝的⽅法名
基于注解的mybatis开发
mybatis的常⽤注解说明
@Insert:实现新增
@Options:主键映射配置
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result⼀起使⽤,封装多个结果集
@ResultMap:实现引⽤@Results定义的封装
@One:实现⼀对⼀结果集封装
@Many:实现⼀对多结果集封装
@SelectProvider: 实现动态SQL映射
/**
* 查询⽤户时,可以同时得到⽤户下所包含的账户信息(注解)
* @return ⽤户
*/
@Select("select * from user")
@Results(value = {
@Result(property = "id",column = "id", id = true),
@Result(property = "username",column = "username"),
@Result(property = "address",column = "address"),
@Result(property = "sex",column = "sex"),
@Result(property = "accounts",many = @Many(select =
"com.dgut.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY),column =
"id")
})
public List<User> findUsersWithAccountsAnno();
}
持久层添加注解
IUserDao
public interface IUserDao {
@Select("select * from user")
public List<User> findAll();
}
mabatis主配置文件
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 resource="jdbc.properties">
</properties>
<!-- 配置环境 -->
<environments default="development">
<!-- 配置mysql环境 -->
<environment id="development">
<!-- 配置事务 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源连接池 -->
<dataSource type="POOLED">
<!-- 这⾥⽤得是mysql8的驱动包 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 告知mybatis映射配置的位置 -->
<mappers>
<mapper class="com.dgut.dao.IUserDao"/>
</mappers>
</configuration>
日志
由于MyBatis默认使⽤log4j输出⽇志信息,所以如果要查看控制台的输出SQL语句,那么就需要在
classpath路径下配置其⽇志⽂件。在resourses根目录下新建⼀个log4j.properties⽂件
log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=file.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
CRUD操作
新增⽤户id的返回值:
新增⽤户后,同时还要返回当前新增⽤户的id值,因为id是由数据库的⾃动增⻓来实现的,所
以就相当于我们要在新增后将⾃动增⻓auto_increment的值返回。
<insert id="insertUser" parameterType="com.dgut.domain.User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user(username,sex,birthday,address) values (#{username},#{sex},#{birthday},#{address})
</insert>
模糊查询:
接口方法
/**
* 根据名称模糊查询
*/
List<User> findByName(String username);
映射配置文件
<select id="findByName" resultType="com.dgut.domain.User parameterType="String">
select * from user where username like #{username}
//select * from user where username like '%${value}%'
</select>
测试类
@Test public void testFindByName() throws Exception {
List<User> users = userDao.findByName("%王%");
//List<User> users = userDao.findByName("王");
System.out.println(users);
}
注意:
#{}表示⼀个占位符号
通过#{}可以实现preparedStatement向占位符中设置值,⾃动进⾏java类型和jdbc类型转换,#{}
可以有效防⽌sql注⼊。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简
单类型值,#{}括号中可以是value或其它名称。
当实体属性与数据库表属性不一致时可通过以下方式进行查询
<select id="findAll2" resultType="com.dgut.domain.User2">
select id as userId,username as userName,birthday as userBirthday,
sex as userSex,address as userAddress from user
</select>
resultMap结果类型
resultMap标签可以建⽴查询的列名和实体类的属性名称不⼀致时建⽴对应关系。从⽽实现封装。
在select标签中使⽤resultMap属性指定引⽤即可。同时resultMap可以实现将查询结果映射为复
杂类型的pojo,⽐如在查询结果映射对象中包括pojo和list实现⼀对⼀查询和⼀对多查询。
<!--
建⽴User实体和数据库表的对应关系
type属性:指定实体类的全限定类名
id属性:给定⼀个唯⼀标识,是给查询select标签引⽤⽤的。
-->
<resultMap id="user2Map" type="com.dgut.domain.User2">
<!-- id标签:⽤于指定主键字段
result标签:⽤于指定⾮主键字段
column属性:⽤于指定数据库列名
property属性:⽤于指定实体类属性名称
-->
<!--主键映射-->
<id column="id" property="userId"/>
<!--普通属性映射-->
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
<result column="address" property="userAddress"/>
<result column="birthday" property="userBirthday"/>
</resultMap>
映射配置
<select id="findAll2" resultMap="user2Map">
select * from user
</select>
动态SQL
编写dao接⼝
/**
* 多条件查询
*/
public List<User> findByUser(User user);
编写映射⽂件
<select id="findByUser" resultType="com.dgut.domain.User“ ”parameterType="com.dgut.domain.User">
select * from user where 1 = 1
<if test="username != null">
and username like #{username}
</if> <if test="sex != null">
and sex = #{sex}
</if>
</select>
为了简化上⾯where 1=1的条件拼装,可以采⽤where标签来简化开发。
<select id="findByUser" resultType="com.dgut.domain.User“ parameterType="com.dgut.domain.User">
select * from user
<where>
<if test="username != null">
and username like #{username}
</if> <if test="sex != null">
and sex = #{sex}
</if>
</where>
</select>
foreach标签
在进⾏范围查询时,就要将⼀个集合中的值,作为参数动态添加进来
<select id="findByQvo" resultType="com.dgut.domain.User“ parameterType="com.dgut.domain.QueryVO">
select * from user
<where>
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open=" and id in (" close=")“ item="abc" separator=",">
#{abc}
</foreach>
</if>
</where>
</select>
标签⽤于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,⽣成的变量名
sperator:代表分隔符
mybatis多表查询
⼀对⼀查询(多对⼀)
<?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.dgut.dao.IAccountDao">
<resultMap id="accountMap" type="com.dgut.domain.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" javaType="com.dgut.domain.User">
<id column="id" property="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap
<select id="findAll" resultMap="accountMap">
select a.*,u.username,u.birthday,u.sex,u.address from `user` u,account a WHERE a.uid = u.id
</select>
</mapper>
⼀对多查询
需求: 查询所有⽤户信息及⽤户关联的账户信息。
分析: ⽤户信息和他的账户信息为⼀对多关系,并且查询过程中如果⽤户没有账户信息,此时也
要将⽤户信息查询出来,我们想到了左外连接查询⽐较合适。
<resultMap id="userAccountMap" type="com.dgut.domain.User"> <id column="id" property="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<!-- collection是⽤于建⽴⼀对多中集合属性的对应关系 ofType⽤于指定集合元素的
数据类型 -->
<collection property="accounts" ofType="com.dgut.domain.Account"> <id property="id" column="uid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
<select id="findALL" resultMap="userAccountMap">
SELECT user.*,account.ID as uid,account.MONEY from user LEFT JOIN account ON user.id = account.UID
</select>
多对多
示例:⽤户和⻆⾊ ⼀个⽤户可以有多个⻆⾊ ⼀个⻆⾊可以赋予多个⽤户 步骤: 1、建⽴两张表:
⽤户表,⻆⾊表 让⽤户表和⻆⾊表具有多对多的关系。需要使⽤中间表,中间表中包含各⾃的主
键,在中间表中是外键。 2、建⽴两个实体类:⽤户实体类和⻆⾊实体类 让⽤户和⻆⾊的实体类
能体现出来多对多的关系 各⾃包含对⽅⼀个集合引⽤ 3、建⽴两个配置⽂件 ⽤户的配置⽂件 ⻆⾊
的配置⽂件 4、实现配置: 当我们查询⽤户时,可以同时得到⽤户所包含的⻆⾊信息 当我们查询
⻆⾊时,可以同时得到⻆⾊的所赋予的⽤户信息
<?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.dgut.dao.IRoleDao"> <resultMap id="roleMap" type="com.dgut.domain.Role"> <id property="id" column="rid"/>
<result property="roleName" column="role_name"/>
<result property="roleDes" column="role_desc"/>
<collection property="users" ofType="com.dgut.domain.User"> <id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</collection>
</resultMap>
<select id="findAllRole" resultMap="roleMap">
SELECT role.id as rid,role.ROLE_NAME,role.ROLE_DESC,user.* FROM role LEFT JOIN user_role ON role.ID = user_role.RID LEFT JOIN `user` ON user_role.UID = `user`.id
</select>
</mapper>
Mybatis延迟加载策略
延迟加载: 就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加
载.
好处:先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查
询多张表速度要快。
坏处: 因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也
要消耗时间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。
需求:
查询账户(Account)信息并且关联查询⽤户(User)信息。如果先查询账户(Account)信息即可满⾜要
求,当我们需要查询⽤户(User)信息时再查询⽤户(User)信息。把对⽤户(User)信息的按需去查询
就是延迟加载。
mybatis实现多表操作时,我们使⽤了resultMap来实现⼀对⼀,⼀对多,多对多关系的操作。主
要是通过association、collection实现⼀对⼀及⼀对多映射。association、collection具备延迟加
载功能。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace命名空间写当前你想映射的接⼝-->
<mapper namespace="com.dgut.dao.IAccountDao">
<!-- 建⽴对应关系 -->
<resultMap id="findAllMap" type="account"> <id property="id" column="accountId"/>
<result property="money" column="money"/>
<!-- 它是⽤于指定从表⽅的引⽤实体属性的
select: 填写我们要调⽤的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
-->
<association property="user" javaType="user"
select="com.dgut.dao.IUserDao.findById" column="id">
</association>
</resultMap> <select id="findAll" resultMap="findAllMap">
SELECT * FROM account
</select>
</mapper>
需求: 完成加载⽤户对象时,查询该⽤户所拥有的账户信息。
IUserDao.xml
<resultMap id="findUsersWithAccountsMap2" type="user"> <id property="id" column="id"/>
<result property="username" column="username"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="address" column="address"/>
<!--
collection是⽤于建⽴⼀对多中集合属性的对应关系
ofType⽤于指定集合元素的数据类型
select是⽤于指定查询账户的唯⼀标识(账户的dao全限定类名加上⽅法名称)
column是⽤于指定使⽤哪个字段的值作为条件查询 -->
<collection property="accounts" ofType="com.dgut.domain.Account"
select="com.dgut.dao.IAccountDao.findByUid" column="id">
</collection>
</resultMap> <select id="findUsersWithAccounts2" resultMap="findUsersWithAccountsMap2">
select * from user
</select>
IAccountDao.xml
<select id="findByUid" resultType="account">
select * from account where uid = #{uid}
</select>
开启Mybatis的延迟加载策略
我们需要在Mybatis的配置⽂件SqlMapConfig.xml⽂件中添加延迟加载的配置。
<!-- 开启延迟加载的⽀持 -->
<settings> <setting name="lazyLoadingEnabled" value="true"/>
</settings>
Mybatis缓存
像⼤多数的持久化框架⼀样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从
⽽提⾼性能。
Mybatis中缓存分为⼀级缓存,⼆级缓存。
⼀级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。
当进行两次同样的查询时,但最后只执⾏了⼀次数据库操作,这就是Mybatis提供给我们的⼀级缓存在起作⽤了。因为⼀级缓存的存在,导致第⼆次查询时,并没有发出sql语句从数据库中查询数据,⽽是从⼀级缓存中查询。
⼀级缓存是SqlSession范围的缓存,当调⽤SqlSession的修改,添加,删除,commit(),close()等⽅法
时,就会清空⼀级缓存。
⼆级缓存是mapper映射级别的缓存,多个SqlSession去操作同⼀个Mapper映射的sql语句,多个
SqlSession可以共⽤⼆级缓存,⼆级缓存是跨SqlSession的。
二级缓存结构图
⾸先开启mybatis的⼆级缓存。
<settings>
<!-- 开启⼆级缓存的⽀持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为cacheEnabled的取值默认就为true,所以这⼀步可以省略不配置。为true代表开启⼆级缓存;为
false代表不开启⼆级缓存。
配置相关的Mapper映射⽂件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace命名空间写当前你想映射的接⼝-->
<mapper namespace="com.dgut.dao.IUserDao">
<!-- 开启⼆级缓存的⽀持 -->
<cache/>
</mapper>
配置statement上⾯的useCache属性
<select id="findById" parameterType="Integer" resultType="user"
useCache="true">
select * from user where id = #{id}
</select> 将UserDao.xml映射⽂件中的<select>标签中设置useCache=”true”代表当前这个statement要使⽤
⼆级缓存,如果不使⽤⼆级缓存可以设置为false。
注意:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁⽤⼆级缓存。
sqlSession1去查询⽤户信息,查询到⽤户信息会将查询数据存储到⼆级缓存中。
如果SqlSession3去执⾏相同 mapper映射下sql,执⾏commit提交,将会清空该 mapper映射下
的⼆级缓存区域的数据。
sqlSession2去查询与sqlSession1相同的⽤户信息,⾸先会去缓存中找是否存在数据,如果存在
直接从缓存中取出数据。
注意:
当我们在使⽤⼆级缓存时,所缓存的类⼀定要实现java.io.Serializable接⼝,这种就可以使⽤序列
化⽅式来保存对象。