MyBatis学习笔记
Mybaits学习笔记
入门
- 创建maven工程并导入坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
-
创建实体类和dao接口
User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int ind;
private String username;
private Date birthday;
private String sex;
private String address;
}
IUserDao.java
public interface IUserDao {
//查询所有
List<User> findAll();
}
-
创建Mybaits主配置文件
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>
<!--配置环境-->
<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&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--映射配置文件-->
<mappers>
<mapper resource="com/hzj/dao/IUserDao.xml"/>
</mappers>
</configuration>
- 创建映射配置文件
<?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.hzj.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="com.hzj.entity.User">
select * from user
</select>
</mapper>
- mybatis的映射配置文件位置必须和dao接口的包结构相同
- 映射配置文件的
mapper
标签namespace
属性必须是dao接口的全限定类名 - 映射配置文件的操作配置
<select>
,id属性的取值必须是dao接口的方法名 - 映射配置文件的
resultType
设置返回值的类型(告诉Mybaits要封装到哪里去)
入门案例
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 中添加注解
public interface IUserDao {
//查询所有
@Select("select * from user")
List<User> findAll();
}
SqlMapConfig.xml 中使用class属性指定被注解的dao全限定类名
<mappers>
<mapper class="com.hzj.dao.IUserDao"/>
</mappers>
mybatis增删改查
执行添加、修改和删除需要提交事务
@After
public void destroy() throws IOException {
//提交事务
session.commit();
//释放资源
session.close();
in.close();
}
添加
IUserDao.java
void save(User user);
IUserDao.xml
<insert id="save" parameterType="com.hzj.entity.User">
insert into user (username, sex)
values (#{username}, #{sex});
</insert>
测试方法 UserDaoTest.java
public void testSave() {
User user = new User();
user.setUsername("test mybatis save");
user.setSex("男");
//执行保存方法
dao.save(user);
}
更新
IUserDao.java
void update(User user);
IUserDao.xml
<update id="update" parameterType="com.hzj.entity.User">
update user
set username = #{username},
sex = #{sex}
where id = #{id};
</update>
测试方法 UserDaoTest.java
public void testUpdate(){
User user = new User();
user.setUsername("test mybatis update");
user.setSex("男");
user.setId(62);
//执行更新方法
dao.update(user);
}
删除
IUserDao.java
void delete(int id);
IUserDao.xml
参数类型只有一个基本类型时,#{}里随便写,表示占位符
<delete id="delete" parameterType="int">
delete
from user
where id = #{id};
</delete>
测试方法 UserDaoTest.java
public void testDelete(){
dao.delete(63);
}
根据id查询
IUserDao.java
User findById(int id);
IUserDao.xml
resultType表示返回值类型
<select id="findById" parameterType="int" resultType="com.hzj.entity.User">
select * from user where id = #{id};
</select>
测试方法 UserDaoTest.java
public void testFindById(){
User user = dao.findById(46);
System.out.println(user);
}
模糊查询
IUserDao.java
List<User> findByName(String username);
IUserDao.xml
<select id="findByName" parameterType="String" resultType="com.hzj.entity.User">
select * from user where username like #{username};
</select>
测试方法 UserDaoTest.java
public void testFindByName(){
//需要加百分号
List<User> list = dao.findByName("%王%");
for (User user : list) {
System.out.println(user);
}
}
另一种方式(不常用) Statement的字符串拼接 有安全问题
UserDao.xml
<select id="findByName" parameterType="String" resultType="com.hzj.entity.User">
select * from user where username like '%${value}%';
</select>
测试方法 UserDaoTest.java
public void testFindByName(){
//不需要加百分号
List<User> list = dao.findByName("王");
}
获取新增存数据的id
IUserDao.xml
<insert id="save" parameterType="com.hzj.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
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 查询条件对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryVo {
private User user;
}
IUserDao.java
List<User> findByVo(QueryVo vo);
IUserDao.xml
<select id="findByVo" parameterType="com.hzj.entity.QueryVo" resultType="com.hzj.entity.User">
select * from user where username like #{user.username};
</select>
测试方法 UserDaoTest.java
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改成这样
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int userId;
private String userName;
private String userSex;
}
而数据库列名分别为 id username sex
在IUserDao.xml中配置查询结果列名和实体类属性的对应关系
<resultMap id="userMap" type="com.hzj.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
<?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&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/hzj/dao/IUserDao.xml"/>
</mappers>
</configuration>
- 外部引用的方式
resource(常用):用于执行配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下
url:要求按照URL的写法来写地址
<properties resource="JdbcConfig.properties"/>
JdbcConfig.properties
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包中的别名
<typeAliases>
<!--<typeAlias type="com.hzj.entity.User" alias="user"/>-->
<package name="com.hzj.entity"/>
</typeAliases>
typeAlias用于配置别名
type属性指的是全限定类名
alias属性指定的是别名,当指定了别名,就不再区分大小写
package用于指定要配置别名的包 当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写
<mappers>
<!--<mapper resource="com/hzj/dao/IUserDao.xml"/>-->
<!--用于指定dao接口所在的包,当指定了之后就不需要在写resource或者class了-->
<package name="com.hzj.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标签
<!--根据条件查询-->
<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
<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代表分隔符
<!--根据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语句
<sql id="defaultUser">
select * from user
</sql>
在别的地方引用
<!--根据id查询用户-->
<select id="findById" parameterType="int" resultType="user">
<include refid="defaultUser"/> where id = #{id};
</select>
多表操作(待填坑)
一对多
从表实体应该包含一个主表实体的对象引用
一对多关系映射:主表实体应该包含从表的集合引用
多对多
没看懂……以后填坑
延迟加载
延迟加载:在真正使用数据的时候才发起查询,不用的时候不查询。按需查询(懒加载)
立即加载:不管用不用,只要一调用方法,马上发起查询
在对应的四种表关系中:
- 一对多、多对多:通常采用延迟加载。
- 多对一、一对一:通常情况下,采用立即加载。
在主配置文件 SqlMapConfig.xml中开启mybatis支持延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
多对一的延迟加载
- 查询账户对应的用户
<!--定义封装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.hzj.dao.IUserDao.findById"/>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="accountUserMap">
select *
from account;
</select>
select:对应的查找一个的方法 (好像是这样。不确定)
mybatis中的缓存
一级缓存
- mybatis中一级缓存指的是SqlSession对象的缓存。
- 执行查询结果后,查询的结果会存入到SqlSession提供的一片区域中。
- 该区域的结构是一个Map。当再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话那来直接用
- 当SqlSession对象消失时,mybatis的一级缓存也就消失了。
代码实现
@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.hzj.entity.User@2e6a8155
com.hzj.entity.User@2e6a8155
true
关闭sqlSession清空缓存
@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.hzj.entity.User@2e6a8155
中间再次执行了一次sql语句
com.hzj.entity.User@52719fb6
false
session.clearCache();//此方法也可以清空缓存
触发清空一级缓存的情况
当调用SqlSession的 修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
二级缓存
-
mybatis中二级缓存指的是SqlSessionFactory对象的缓存。由同一个SqlSessionFactory创建的SqlSession共享其缓存。
-
二级缓存的使用步骤
- 让mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
<settings> <setting name="cacheEnabled" value="true"/> </settings>
- 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
<!--开启user支持二级缓存--> <cache/>
-
让当前的操作支持二级缓存(select标签中配置)
<select>标签上添加 `useCache="true"` 属性
-
注意: 在mybatis中使用二级缓存就必须要把实体类序列化
implements Serializable
。
代码实现
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.hzj.entity.User@60099951
com.hzj.entity.User@f78a47e
false
- 二级缓存中存放的是数据,而不是对象。虽然没有发起查询,但不是同一个对象,所以输出false了。
注解开发
单表增删改查
IUserDao.java
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();
}
如果实体类和数据库列名不对应
在方法上添加一下注解
@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)
注解,即可开启二级缓存