MyBatis开发步骤
- 创建新模块
- 加入MyBatis和数据库驱动jar包
- 编写用户实体类(User)
- 准备核心配置文件:sqlMapConfig.xml
- 编写用户 dao 接口(UserMapper)
- 编写用户 dao 接口映射文件(UserMapper.xml)
- 编写测试代码
环境搭建
-
创建模块:mybatis_helloworld
-
加入MyBatis相关jar包:在模块下新建lib文件夹,复制mybatis框架jar包到lib文件夹下 MyBatis框架包 MySQL数据库驱动包 log4j 日志包
MyBatis配置文件分类
MyBatis为了灵活,将数据库相关数据和SQL语句写到XML配置文件中。
- 核心配置文件:配置数据库的连接url,账号,密码,一般核心配置文件的名称:sqlMapConfig.xml
- 接口的映射文件:配置要执行的SQL语句,一般名称: XxxMapper.xml
核心配置文件配置了4个参数:
- driver:配置驱动的类名
- url:配置数据库的URL
- username:配置数据库的账号
- password:配置数据库的密码
核心配置文件sqlMapConfig.xml
namespace: 包名.接口名
id: 方法名
resultType: 方法的返回值类型
parameterType: 参数类型
#{xxx}: 首先使用?占位, 后面将xxx的值赋值给?
select标签主体内容: 查询的SQL语句
delete标签: 表示编写删除的SQL语句
insert标签: 表示添加数据的SQL语句
update: 表示执行修改的SQL语句
MyBatis入门案例:测试类
三大对象
- SqlSessionFactoryBuilder:负责构建SqlSessionFactory
- SqlSessionFactory:创建SqlSession实例的工厂
- SqlSession:用于执行SQL操作的对象
编写代码流程
- 创建SqlSessionFactoryBuilder对象
- 得到会话工厂SqlSessionFactory类
- 得到SqlSession对象
- 通过SqlSession对象得到Mapper接口的代理对象
- Mapper接口的代理对象执行数据库的查询操作
// MyBatis测试类
public class TestMyBatis {
// 测试方法
@Test
public void testMyBatis() throws IOException {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2.得到会话工厂SqlSessionFactory类
// 类路径:加载文件返回流, /表示到了src
// InputStream in = TestMyBatis.class.getResourceAsStream("/sqlMapConfig.xml");
// Resources: 这个类是MyBatis提供的,就是加载src下的资源,不要写/开头
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = builder.build(in);
// 3.得到SqlSession对象, SqlSession相当于数据库的连接Connection
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.通过SqlSession对象得到Mapper接口的代理对象
// getMapper返回接口的实现类.(使用动态代理生成接口的实现类)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5.Mapper接口的代理对象执行数据库的查询操作
List<User> users = mapper.findAllUsers();
User userById = mapper.findUserById(2);
// 6.关闭资源
sqlSession.close();
for (User user : users) {
System.out.println(user);
}
}
}
查询新增记录的主键值
子元素<selectKey>(常用)
<!--insert: 表示添加数据-->
<insert id="addUser" parameterType="com.itheima.entity.User">
INSERT INTO user VALUES (null, #{username}, #{birthday}, #{sex}, #{address});
<!--selectKey:获取插入数据的自增主键
keyColumn: 数据库表中的主键字段
keyProperty: 类中存放主键的成员变量
resultType: 表中主键的类型
order: 执行的时机: BEFORE: 在执行SQL语句前查询主键 AFTER在执行SQL语句后查询主键
-->
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID();
</selectKey>
</insert>
在insert标签中增加属性(了解)
<!--insert:
useGeneratedKeys: 获取插入数据的自增主键
keyColumn: 数据库表中的主键字段
keyProperty: 类中存放主键的成员变量
-->
<insert id="addUser2" parameterType="com.itheima.entity.User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO user VALUES (null, #{username}, #{birthday}, #{sex}, #{address});
</insert>
核心配置文件
properties标签
properties的作用
将外面的属性文件(.properties)加载进来。在后面就可以引用属性文件中的键和值
操作步骤
编写数据库连接属性资源文件(jdbc.properties)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
在核心配置文件中通过properties标签加载jdbc.properties属性资源文件
<!--
属性:
resource: 指定类路径下Java的属性文件
-->
<properties resource="db.properties">
</properties>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--1.3配置连接池需要的参数-->
<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>
小结
-
properties标签作用是什么?
引入外部properties文件
-
如何使用外部properties文件中的数据
${键} : 取出这个键对应的值
typeAliases别名
typeAlias标签的作用:给类取别名
<!--
定义别名
typeAlias 子元素
type: 类全名
alias: 别名,可以省略。默认使用类名做为别名,不区分大小写
package子元素:
给包里面所有类取别名, 别名就是类名,不区分大小写
-->
<typeAliases>
<typeAlias type="com.kane.entity.User" alias="user"/>
<package name="com.kane.entity"/>
</typeAliases>
package标签的作用
<typeAliases>
<!--package标签:包扫描, 扫描这个包中的所有类,给这些类全部都取别名,别名就是类名小写
name:包名-->
<package name="com.kane.entity"/>
</typeAliases>
mappers(映射器)
加载单个映射文件
mapper标签的属性
<!--映射器-->
<mappers>
<!--
resource: 指定类路径下映射文件,注:路径使用/做为分隔符,而不是点号
class: 指定使用注解的接口名字
-->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
注:如果是多级目录,是/而不是点号
包扫描加载多个映射文件
包扫描方式加载mapper映射文件
-
要求接口映射文件,与接口要放在同一个目录
-
要求接口映射文件的名称,与接口的名称要一致
<!--配置接口映射文件-->
<mappers>
<!--
package标签:
接口映射文件与接口文件在同一个目录
接口映射文件的名称与接口文件的名称相同
-->
<package name="com.kane.dao"/>
</mappers>
MyBatis模糊查询
在接口映射文件中编写相应的SQL语句
<select id="findUserByname" parameterType="string" resultType="user">
SELECT * FROM user WHERE username LIKE #{username};
</select>
#{} 先使用?占位,后面给?赋值
当参数是基本数据类型或者包装类或者String
#{随便写}
当参数是自定义类型User
#{类中的成员变量名}
${} 字符串拼接,拿到参数和sql拼接(不建议使用)
当参数是基本数据类型或者包装类或者String
${value}
当参数是自定义类型User
${类中的成员变量名}
接口映射文件中方法的的参数类型可以省略
方法的返回值类型不能省略
映射文件的标签
配置mapper映射文件
- 定义resultMap标签
- id标签:映射主键字段,如果列名与属性名相同可以省略
- result标签:映射普通字段,指定哪个属性对应哪个列
- 在查询的结果中使用resultMap
<!--
定义结果映射
id: 映射的唯一标识
type: 最终结果转换后的类型
子元素:
id: 定义主键字段的映射
result: 定义普通的字段的映射
属性:property: 实体类中属性名,column: 表中列名,如果相同可以不写。
->
<resultMap id="u1" type="user">
<id column="id2" property="id"/>
<result column="username2" property="username"/>
<result column="birthday2" property="birthday"/>
</resultMap>
<select id="findUserByIdUseResultMap" parameterType="int" resultMap="u1">
SELECT id id2, username username2, birthday birthday2, sex, address FROM user WHERE id = #{id};
</select>
动态SQL
动态SQL指的是:在程序运行时,根据不同的情况,拼接最终执行的sql语句。
搭建动态SQL的环境
模块名:mybatis_dynamic_sql
if标签
<if test="条件">
SQL语句
</if>
if标签的作用
当if的条件为true就会拼接这个SQL片段
where标签
- 相当于where关键字,自动补全where这个关键字
- 去掉多余的and和or关键字
where标签的作用
1.充当where关键字
2.在需要的时候添加WHERE关键字
3.会去掉多余的AND或OR关键字
set标签
- 用在update语句中,相当于set关键字
- 去掉SQL代码片段中后面多余的逗号
set标签的作用
1.相当于SET关键字,在需要的时候添加SET关键字
2.去掉多余的,
foreach标签
foreach: 遍历数组或集合
collection: 两个取值 遍历数组写array, 遍历集合写list
open: 遍历前添加的内容
close: 遍历后添加的内容
item: 保存遍历得到的元素
separator: 每次遍历后添加的内容
sql和include标签
- sql标签:定义一段SQL语句,起个名字可以重用。
- include标签:引入上面定义的SQL代码段。
动态SQL标签
if标签的格式:
<if test="条件">
SQL片段
</if>
if标签的作用:当条件为true就会拼接SQL片段
<where></where>
where标签的作用:
1.相当于WHERE关键字
2.如果有条件就加上WHERE关键字,没有条件就会自动去掉WHERE关键字
3.去掉多余的AND 或 OR
<set></set>
1.相当于SET关键字
2.去掉多余,
<foreach>
</foreach>
foreach标签的作用: 遍历数组或集合
collection属性: 要遍历的数组或集合,数组写array, 集合写list
open属性: 在遍历前拼接的内容
close属性: 在遍历后拼接的内容
item属性: 保存每次遍历的元素
separator: 分隔符,每次遍历完一个元素后添加的内容
多表关联
一对一关联
一对一关联查询实体类的关系
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 一个用户包含一个扩展信息
private UserInfo userInfo;
}
在接口映射文件种使用哪个标签指定一对一关联
多表查询的时候使用resultMap封装结果时,即使字段名和类的属性名相同也需要指定。可以搞autoMapping=true
<resultMap id="userAndInfoMap" type="user" autoMapping="true">
<!--
association: 配置一对一关系: 一个用户对应一个扩展信息
property: 类中另一方的成员变量名
javaType: 类中另一方的类型
-->
<association property="userInfo" javaType="UserInfo" autoMapping="true">
</association>
</resultMap>
<select id="findUserAndInfo" parameterType="int" resultMap="userAndInfoMap">
SELECT * FROM USER INNER JOIN user_info ON user_info.id=user.id WHERE user.id=#{id};
</select>
一对多关联
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<OrderForm> orders; // 对应的所有订单信息
private UserInfo userInfo; // 对应的用户信息
// 省略getter/setter/toString
}
<resultMap id="UserMoreOrderFormRM" type="user" autoMapping="true">
<!--一对多一定要配置一方的主键,去掉重复的-->
<id column="id" property="id"/>
<!--collection:一对多配置
property: 类中的成员变量名
javaType: 外面的类型
ofType: 里面的类型
-->
<collection property="orders" javaType="list" ofType="orderform" autoMapping="true">
</collection>
</resultMap>
<!--(一对多)查询用户和对应的多个订单-->
<select id="findUserAndOrders" resultMap="UserMoreOrderFormRM">
SELECT * FROM USER INNER JOIN order_form ON user.id=order_form.user_id WHERE user.id=#{uid};
</select>
多对多关联
多对多其实就是分成两个一对多处理
配置接口映射文件
查询一个用户对应多个角色
<resultMap id="userAndRoleMap" type="user" autoMapping="true">
<id column="id" property="id"/>
<!--一对多,多方的信息-->
<collection property="roles" javaType="list" ofType="role" autoMapping="true">
<id column="role_id" property="roleId"/>
</collection>
</resultMap>
<!--通过uid查找用户和多个角色-->
<select id="findUserAndRolesByUserId" resultMap="userAndRoleMap">
SELECT * FROM USER INNER JOIN user_role ON user.id=user_role.user_id
INNER JOIN role ON user_role.role_id=role.role_id WHERE user.id=#{uid};
</select>
一个角色对应多个用户
<resultMap id="roleAndUserMap" type="role" autoMapping="true">
<id column="role_id" property="roleId"/>
<!--配置一对多的多方信息-->
<collection property="users" javaType="list" ofType="user" autoMapping="true">
<id column="id" property="id"/>
</collection>
</resultMap>
<!--通过角色id rid查找角色和多个用户-->
<select id="findRoleAndUsersByRoleId" resultMap="roleAndUserMap">
SELECT * FROM USER INNER JOIN user_role ON user.id=user_role.user_id
INNER JOIN role ON user_role.role_id=role.role_id WHERE role.`role_id`=#{rid};
</select>
延迟加载
延迟加载概念:也叫懒加载。指的是按需加载,在实际用到数据的时候才加载。
如:查询用户信息,不需要他的扩展信息。但后面有可能又需要用到,这时候可以通过延迟加载来实现。当需要扩展信息的时候,再发送一条SQL语句来查询扩展信息。好处是,只有在需要的时候才查询相应数据。提升查询的效率。相当于每次只查询1张表,而不是一次使用表连接查询所有的信息。
<!--association:一对一配置
select: 下一条SQL语句的方法名
column: 这个字段的值会作为下一个SQL语句的参数
fetchType: 加载类型
lazy: 延迟加载(需要使用的时候才加载)
eager: 立即加载(默认)-->
一对一关联查询使用标签:association
一对多关联查询使用标签:collection
association标签: 表示一对一的配置
association标签的属性 | 说明 |
---|---|
property | 另一方的成员变量名 |
column | 这个字段的值,作为下一个查询语句的参数 |
select | 下一条SQL语句 |
fetchType | lazy: 延迟加载 eager: 立即加载 |
collection标签的属性: 配置一对多
collection标签的属性 | 说明 |
---|---|
property | 多放的成员变量 |
column | 这个字段的值作为下一个条SQL语句的参数 |
select | 下一条要执行的SQL语句 |
MyBatis一级缓存
一级缓存是 sqlSession 范围的缓存,只能在同一个 sqlSession 内部有效。它本身已经存在,一级缓存不需要手动处理,可以直接使用。
一级缓存测试
public class TestCache {
// 测试一级缓存
@Test
public void testPrimaryCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class); // 得到代理对象
/*
* 1.先根据id=1去一级缓存找
* 2.没有找到,再查询数据库
* 3.查询到结果后,放入一级缓存
*/
User user1 = userMapper.findUserById(1);
/*
* 1.先根据id=1去一级缓存找
* 2.找到,就直接返回,不查询数据库
*/
User user2 = userMapper.findUserById(1);
session.commit(); // 提交事务,清空一级缓存
session.close();
}
}
清空一级缓存
public class TestCache {
// 测试一级缓存
@Test
public void testPrimaryCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class); // 得到代理对象
/*
* 1.先根据id=1去一级缓存找
* 2.没有找到,再查询数据库
* 3.查询到结果后,放入一级缓存
*/
User user1 = userMapper.findUserById(1);
// 调用clearCache()方法,清空一级缓存的内容
session.clearCache();
// 当调用 SqlSession 的修改、添加、删除、提交、关闭等方法时,一级缓存会被清空。
// userMapper.deleteUserById(5);
/*
* 1.先根据id=1去一级缓存找
* 2.找到,就直接返回,不查询数据库
*/
User user2 = userMapper.findUserById(1);
session.commit(); // 提交事务,清空一级缓存
session.close();
}
}
-
缓存有什么好处?
对于相同的查询语句,只会走一次数据库的查询,轻数据库的压力。
-
一级缓存的范围?
同一个SqlSession有效
-
一级缓存何时失效?
增删改,提交事务,关闭SqlSession主动清空缓存clearCache()
MyBatis二级缓存
二级缓存是 mapper 映射级别缓存,作用范围跨越SqlSession,即可以在多个 SqlSession 之间共享二级缓存
数据。
二级缓存关键点
- 实体类需要实现Serializable接口
- 至少要准备2个SqlSession,再进行测试
修改实体类实现Serializable接口
public class User implements Serializable {
private Integer id; // 主键
private String username; // 用户名
private Date birthday; // 生日
private String sex; // 性别
private String address; // 地址
// 省略其他
}
配置二级缓存
在 sqlMapConfig.xml 配置开启二级缓存,找到settings配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在 UserMapper.xml 开启二级缓存使用
二级缓存还需要在具体的 mapper 映射文件中明确开启,这样做的原因是缓存数据要消耗资源,只有在需要使
用的时候开启,可以避免资源的过度消耗。
<mapper namespace="com.kane.dao.UserMapper">
<!--开启二级缓存,当前Mapper里的所有查询的数据都会放入二级缓存中-->
<cache/>
<select id="findUserById" parameterType="int" resultMap="useMapOne">
SELECT * FROM USER WHERE id=#{uid};
</select>
</mapper>
测试
// 测试二级缓存
@Test
public void testSecondCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session1 = factory.openSession();
UserMapper userMapper1 = session1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
session1.close(); // 需要先关掉第一个sqlsession
SqlSession session2 = factory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
session2.close();
}
二级缓存实现步骤
-
实体类实现Serializable
-
在核心配置文件中配置
<settings> <!--开启二级缓存--> <setting name="cacheEnabled" value="true"/> </settings>
-
在接口映射文件中配置
<mapper namespace="com.kane.dao.UserMapper"> <!--让这个mapper开启二级缓存,这个mapper查询数据就会保存到二级缓存中--> <cache/> </mapper>
-
测试时需要使用两个不同的SqlSession.
注解开发方式
@Insert: 执行添加的SQL语句
@Update: 执行修改的SQL语句
@Delete: 执行删除的SQL语句
@SelectKey说明
属性 | 说明 |
---|---|
statement | 要执行的SQL语句:select last_insert_id() |
keyProperty | 实体类中主键的属性 |
keyColumn | 表中主键的列名 |
resultType | 主键的数据类型 |
before | false 表示after,true表示before |
注解说明
注解 | 属性 | 说明 |
---|---|---|
@Results | 相当于resultMap表示要对结果进行映射 | |
@Result | 对一个字段进行映射 | |
column | 查询的字段名 | |
property | 类中的成员变量名 | |
id | true 表示是主键 |
@Select注解作用:编写查询的SQL语句
@Results注解作用:相当于resultMap标签,对查询的结果进行手动封装(映射)
@Result注解作用:相当于result标签,将查询的字段值保存到类中的成员变量里面
MyBatis注解小结
在注解方式实现基本CRUD操作中,使用的注解有:
注解 | 描述 |
---|---|
@Select | 编写查询的SQL语句 |
@Results | 相当于resultMap,对查询的结果进行手动封装 |
@Result | 相当于result标签,对一个字段进行映射 |
@Update | 编写修改的SQL语句 |
@Delete | 编写删除的SQL语句 |
@Insert | 编写添加的SQL语句 |
@SelectKey | 获取添加数据后的自增的主键 |
@Param(“别名”) | 多个参数时需要取别名,不然会报错 |
复杂关系映射注解介绍
注解 | 描述 | 对应xml配置标签 |
---|---|---|
@One | 用于一对一关联映射 | association |
@Many | 用于一对多的关联映射 | collection |
使用注解多表查询,不能使用内连接一次查出多张表的数据,只能懒加载的形式,分成多条SQL语句。
在UserMapper接口中增加查询方法
-
编写方法:通过id查询用户扩展信息
- 方法名:findUserInfoById
- 使用@Select注解编写SQL语句
-
编写方法:通过id查询1个用户
- 方法名:findUserById
- @Select编写查询
- @Results配置1对1关联映射
UserMapper接口
通过user_id查询当前用户订单的方法
-
编写findOrdersByUserId方法
-
使用@Select注解
-
修改findUserById()方法,增加1对多延迟加载配置
@Result注解属性 | 说明 |
---|---|
column | 这个字段的值会作为下一个SQL语句的参数 |
property | 类中的成员变量名 |
many | 表示一对多的配置 |
@Many注解属性 | 说明 |
---|---|
select | 下一条要执行的SQL语句的方法 |
fetchType | 获取数据的类型 FetchType.DEFAULT(立即加载), FetchType.LAZY(延迟加载) |
@Insert(): 增加数据
@Update(): 修改数据
@Delete(): 删除数据
@Select(): 查询数据