MyBatis学习笔记

mybatis的环境搭建、单表增删改查、基于xml配置的动态sql语句、多表操作

入门

  1. 创建maven工程并导入坐标
1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
  1. 创建实体类和dao接口

    User.java

1
2
3
4
5
6
7
8
9
10
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int ind;
private String username;
private Date birthday;
private String sex;
private String address;
}

​ IUserDao.java

1
2
3
4
public interface IUserDao {
//查询所有
List<User> findAll();
}
  1. 创建Mybaits主配置文件

    SqlMapConfig.xml

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
28
29
30
<?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>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置链接数据库的四个信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/jdbcdb?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!--映射配置文件-->
<mappers>
<mapper resource="com/xzxj/dao/IUserDao.xml"/>
</mappers>

</configuration>
  1. 创建映射配置文件
1
2
3
4
5
6
7
8
9
10
<?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.xzxj.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="com.xzxj.entity.User">
select * from user
</select>
</mapper>
  • mybatis的映射配置文件位置必须和dao接口的包结构相同
  • 映射配置文件的 mapper 标签 namespace 属性必须是dao接口的全限定类名
  • 映射配置文件的操作配置 <select> ,id属性的取值必须是dao接口的方法名
  • 映射配置文件的 resultType 设置返回值的类型(告诉Mybaits要封装到哪里去)

入门案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyBatisTest {
public static void main(String[] args) throws IOException {
//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接口的代理对象
IUserDao dao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> list = dao.findAll();
for (User user : list) {
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}

注解配置入门

IUserDao.java 中添加注解

1
2
3
4
5
public interface IUserDao {
//查询所有
@Select("select * from user")
List<User> findAll();
}

SqlMapConfig.xml 中使用class属性指定被注解的dao全限定类名

1
2
3
<mappers>
<mapper class="com.xzxj.dao.IUserDao"/>
</mappers>

mybatis增删改查

执行添加、修改和删除需要提交事务

1
2
3
4
5
6
7
8
@After
public void destroy() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
in.close();
}

添加

IUserDao.java

1
void save(User user);

IUserDao.xml

1
2
3
4
<insert id="save" parameterType="com.xzxj.entity.User">
insert into user (username, sex)
values (#{username}, #{sex});
</insert>

测试方法 UserDaoTest.java

1
2
3
4
5
6
7
public void testSave() {
User user = new User();
user.setUsername("test mybatis save");
user.setSex("男");
//执行保存方法
dao.save(user);
}

更新

IUserDao.java

1
void update(User user);

IUserDao.xml

1
2
3
4
5
6
<update id="update" parameterType="com.xzxj.entity.User">
update user
set username = #{username},
sex = #{sex}
where id = #{id};
</update>

测试方法 UserDaoTest.java

1
2
3
4
5
6
7
8
public void testUpdate(){
User user = new User();
user.setUsername("test mybatis update");
user.setSex("男");
user.setId(62);
//执行更新方法
dao.update(user);
}

删除

IUserDao.java

1
void delete(int id);

IUserDao.xml

参数类型只有一个基本类型时,#{}里随便写,表示占位符

1
2
3
4
5
<delete id="delete" parameterType="int">
delete
from user
where id = #{id};
</delete>

测试方法 UserDaoTest.java

1
2
3
public void testDelete(){
dao.delete(63);
}

根据id查询

IUserDao.java

1
User findById(int id);

IUserDao.xml

resultType表示返回值类型

1
2
3
<select id="findById" parameterType="int" resultType="com.xzxj.entity.User">
select * from user where id = #{id};
</select>

测试方法 UserDaoTest.java

1
2
3
4
public void testFindById(){
User user = dao.findById(46);
System.out.println(user);
}

模糊查询

IUserDao.java

1
List<User> findByName(String username);

IUserDao.xml

1
2
3
<select id="findByName" parameterType="String" resultType="com.xzxj.entity.User">
select * from user where username like #{username};
</select>

测试方法 UserDaoTest.java

1
2
3
4
5
6
7
public void testFindByName(){
//需要加百分号
List<User> list = dao.findByName("%王%");
for (User user : list) {
System.out.println(user);
}
}

另一种方式(不常用) Statement的字符串拼接 有安全问题

UserDao.xml

1
2
3
<select id="findByName" parameterType="String" resultType="com.xzxj.entity.User">
select * from user where username like '%${value}%';
</select>

测试方法 UserDaoTest.java

1
2
3
4
public void testFindByName(){
//不需要加百分号
List<User> list = dao.findByName("王");
}

获取新增存数据的id

IUserDao.xml

1
2
3
4
5
6
7
8
<insert id="save" parameterType="com.xzxj.entity.User">
-- keyProperty:实体类属性 keyColumn:数据库列名
-- order:插入后的行为 resultType:返回值类型
<selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="int">
select last_insert_id();
</selectKey>
insert into user (username, sex)values (#{username}, #{sex});
</insert>

测试方法 UserDaoTest.java

1
2
3
4
5
6
7
8
9
10
public void testSave() {
User user = new User();
user.setUsername("test mybatis save");
user.setSex("男");

System.out.println("保存操作之前:" + user);
//执行保存方法
dao.save(user);
System.out.println("保存操作之后:" + user);
}

输出结果

保存操作之前:User(id=0, username=test mybatis save, sex=男)
保存操作之后:User(id=64, username=test mybatis save, sex=男)

使用实体类的包装对象作为查询条件

  • 由多个对象组成一个对象作为查询条件

QueryVo.java 查询条件对象

1
2
3
4
5
6
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryVo {
private User user;
}

IUserDao.java

1
List<User> findByVo(QueryVo vo);

IUserDao.xml

1
2
3
<select id="findByVo" parameterType="com.xzxj.entity.QueryVo" resultType="com.xzxj.entity.User">
select * from user where username like #{user.username};
</select>

测试方法 UserDaoTest.java

1
2
3
4
5
6
7
8
public void testFindByVo(){
User user = new User();
user.setUsername("%王%");
List<User> list = dao.findByVo(new QueryVo(user));
for (User u : list) {
System.out.println(u);
}
}

mybatis的参数

如果实体类属性和数据库列名不对应

User.java改成这样

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int userId;
private String userName;
private String userSex;
}

而数据库列名分别为 id username sex

在IUserDao.xml中配置查询结果列名和实体类属性的对应关系

1
2
3
4
5
6
7
<resultMap id="userMap" type="com.xzxj.entity.User">
<!--主键字段对应-->
<id property="userId" column="id"/>
<!--非主键字段对应-->
<result property="userName" column="username"/>
<result property="userSex" column="sex"/>
</resultMap>

在后面的sql语句的属性中 resultType 改为 resultMap 并指定为 resultMap 的id

配置文件的其他

properties标签的使用及细节

SqlMapConfig.xml

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
28
29
30
31
<?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>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>

<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="com/xzxj/dao/IUserDao.xml"/>
</mappers>

</configuration>
  • 外部引用的方式

resource(常用):用于执行配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下

url:要求按照URL的写法来写地址

1
<properties resource="JdbcConfig.properties"/>

JdbcConfig.properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
username=root
password=root

typeAliases标签 和 package标签

SqlMapConfig.xml

使用typeAliases配置别名,只能配置entity包中的别名

1
2
3
4
<typeAliases>
<!--<typeAlias type="com.xzxj.entity.User" alias="user"/>-->
<package name="com.xzxj.entity"/>
</typeAliases>

typeAlias用于配置别名

type属性指的是全限定类名

alias属性指定的是别名,当指定了别名,就不再区分大小写

package用于指定要配置别名的包 当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写

1
2
3
4
5
<mappers>
<!--<mapper resource="com/xzxj/dao/IUserDao.xml"/>-->
<!--用于指定dao接口所在的包,当指定了之后就不需要在写resource或者class了-->
<package name="com.xzxj.dao"/>
</mappers>

连接池

mybatis连接池提供了3种方式的配置

​ 配置文件的位置

  • 主配置文件SqlMapConfig.xml中的 dataSource 标签,type 属性表示采用何种链接方式。
    • 取值:
      • POOLED 采用传统的javax.sql.dataSource规范中的连接池。mybatis中有针对规范的实现。
      • UNPOOLED 采用传统的获取链接的方式,虽然也实现了javax.sql.dataSource接口,但是并没有使用池的思想。
      • JNDI 采用服务器提供的JNDI技术实现,来获取dataSource对象。不同的服务器所能拿到的dataSource是不一样的。注意:如果不是web或者maven的war工程,是不能使用的。使用的Tomcat服务器,采用的就是dbcp连接池。

事务控制

事务是恢复和并发控制的基本单位。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

  • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

解决办法:四种隔离级别

mybatis中通过SqlSession对象的 commit() 方法和 rollback() 方法实现事务的提交和回滚。

基于xml配置的动态sql语句

if标签

1
2
3
4
5
6
7
8
9
10
<!--根据条件查询-->
<select id="findByCondition" resultType="user" parameterType="user">
select * from user where 1 = 1
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
</select>

where标签

不用写where 1 = 1

1
2
3
4
5
6
7
8
9
10
11
<select id="findByCondition" resultType="user" parameterType="user">
select * from user
<where>
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
</where>
</select>

foreach标签

collection 代表要遍历的集合元素

item 代表遍历集合的每个元素,生成的变量名

separator代表分隔符

1
2
3
4
5
6
7
8
9
10
11
<!--根据QueryVo中的id集合实现查询用户列表-->
<select id="findInIds" resultType="user" 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}<!--和item一样-->
</foreach>
</if>
</where>
</select>

sql标签

抽取重复的sql语句

1
2
3
<sql id="defaultUser">
select * from user
</sql>

在别的地方引用

1
2
3
4
<!--根据id查询用户-->
<select id="findById" parameterType="int" resultType="user">
<include refid="defaultUser"/> where id = #{id};
</select>

多表操作(待填坑)

一对多

从表实体应该包含一个主表实体的对象引用

一对多关系映射:主表实体应该包含从表的集合引用

多对多

没看懂……以后填坑

延迟加载

延迟加载:在真正使用数据的时候才发起查询,不用的时候不查询。按需查询(懒加载)

立即加载:不管用不用,只要一调用方法,马上发起查询

在对应的四种表关系中:

  • 一对多、多对多:通常采用延迟加载。
  • 多对一、一对一:通常情况下,采用立即加载。

在主配置文件 SqlMapConfig.xml中开启mybatis支持延迟加载

1
2
3
4
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

多对一的延迟加载

  • 查询账户对应的用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--定义封装Account和User的ResultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--一对一的关系映射-->
<association property="user" column="uid" javaType="user" select="com.xzxj.dao.IUserDao.findById"/>
</resultMap>

<!--查询所有-->
<select id="findAll" resultMap="accountUserMap">
select *
from account;
</select>

select:对应的查找一个的方法 (好像是这样。不确定)

mybatis中的缓存

一级缓存

  • mybatis中一级缓存指的是SqlSession对象的缓存。
  • 执行查询结果后,查询的结果会存入到SqlSession提供的一片区域中。
  • 该区域的结构是一个Map。当再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话那来直接用
  • 当SqlSession对象消失时,mybatis的一级缓存也就消失了。

代码实现

1
2
3
4
5
6
7
8
@Test
public void testFirstLevelCache() {
User u1 = dao.findById(41);
System.out.println(u1.hashCode());
User u2 = dao.findById(41);
System.out.println(u2.hashCode());
System.out.println(u1 == u2);
}

​ 输出结果

com.xzxj.entity.User@2e6a8155
com.xzxj.entity.User@2e6a8155
true

​ 关闭sqlSession清空缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testFirstLevelCache() {
User u1 = dao.findById(41);
System.out.println(u1.hashCode());

session.close();
session = factory.openSession();
dao = session.getMapper(IUserDao.class);

User u2 = dao.findById(41);
System.out.println(u2.hashCode());
System.out.println(u1 == u2);
}

​ 输出结果

com.xzxj.entity.User@2e6a8155
中间再次执行了一次sql语句
com.xzxj.entity.User@52719fb6
false

1
session.clearCache();//此方法也可以清空缓存

触发清空一级缓存的情况

当调用SqlSession的 修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

二级缓存

  • mybatis中二级缓存指的是SqlSessionFactory对象的缓存。由同一个SqlSessionFactory创建的SqlSession共享其缓存。

  • 二级缓存的使用步骤

    1. 让mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)

      1
      2
      3
      <settings>
      <setting name="cacheEnabled" value="true"/>
      </settings>
    2. 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)

      1
      2
      <!--开启user支持二级缓存-->
      <cache/>
    3. 让当前的操作支持二级缓存(select标签中配置)

      1
      <select>标签上添加 `useCache="true"` 属性
  • 注意: 在mybatis中使用二级缓存就必须要把实体类序列化 implements Serializable

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testSecondLevelCache() {
SqlSession session1 = factory.openSession();
IUserDao dao1 = session1.getMapper(IUserDao.class);
User u1 = dao1.findById(41);
System.out.println(u1);
session1.close();//一级缓存消失

SqlSession session2 = factory.openSession();
IUserDao dao2 = session2.getMapper(IUserDao.class);
User u2 = dao2.findById(41);
System.out.println(u2);
session2.close();//一级缓存消失

System.out.println(u1 == u2);
}

输出结果

com.xzxj.entity.User@60099951
com.xzxj.entity.User@f78a47e
false

  • 二级缓存中存放的是数据,而不是对象。虽然没有发起查询,但不是同一个对象,所以输出false了。

注解开发

单表增删改查

IUserDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IUserDao {

@Select("select * from user")
List<User> findAll();

@Insert("insert into user (username, sex) values (#{username}, #{sex})")
void save(User user);

@Update("update user set username = #{username}, sex = #{sex} where id = #{id} ")
void update(User user);

@Delete("delete from user where id = #{id}")
void delete(int id);

@Select("select * from user where id = #{id}")
User findById(int id);

@Select("select * from user where username like #{username}")
//@Select("select * from user where username like '%${value}%'")
List<User> findByName(String username);

@Select("select count(*) from user")
int findTotal();
}

如果实体类和数据库列名不对应

​ 在方法上添加一下注解

1
2
3
4
5
@Results(id = "userMap", value = {
@Result(id = true, property = "uid",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userSex",column = "sex"),
})

​ 然后在其他方法上就可以用 @ResultMap 注解 指定id的属性

多表查询的操作

缓存的配置

在IUserDao.java上添加 @CacheNamespace(blocking = true) 注解,即可开启二级缓存

  • Copyrights © 2020-2023 夕子学姐
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信