1、注解开发介绍
在过去使用框架开发项目,基本都采用xml作为框架的核心配置文件存在,但是这种方式开发效率还是比较地下、不灵活。
现在企业开发为了能够更快的提高开发效率,必然会使用企业级框架进行项目开发,而现在主流的框架都支持基于注解的方式开发项目。
mybatis框架已经全面支持基于注解的开发。
使用mybatis的注解开发,并不能省略mybatis的核心配置文件,而注解仅仅只是代替Mapper文件。
MyBatis 的注解开发主要代替了以下功能:
- 替代映射器接口(Mapper XML)
- 使用@Mapper标注接口为映射器接口
- 使用@Select、@Update等注解写SQL语句
- 替代结果映射(ResultMap)
- 使用@Results和@Result注解配置结果映射
- 替代字段与属性映射
- 使用@Column注解指定数据库列名
- 使用@Id注解指定主键字段
- 替代关联查询
- 使用@One、@Many注解配置一对一、一对多关系
- 替代动态SQL
- 使用@SelectProvider、@UpdateProvider等注解执行动态SQL
- 替代缓存配置
- 使用@CacheNamespace、@CacheFlush等注解配置缓存
注解开发使配置更简单,直接在接口及字段上通过注解即可实现XML方式的大部分功能。
但注解方式有限制。比如复杂的结果映射、动态SQL等还是建议用XML方式配置。所以在复杂场景下,注解+XML配置可以发挥两者优势。
2、Mybatis注解开发
mybatis注解开发主要介绍:
1、 注解开发的环境搭建
2、 单表的CRUD操作(使用代理方式)
3、 多表查询操作
4、 缓存的配置
2.1、注解开发环境搭建
2.1.1、创建maven项目
选择File > New > Project
,进入创建界面
选择New Project
,右侧填写项目名,选择项目存储路径,选择语言,构建工具,jdk版本,maven高级设置:
2.1.2、maven的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qbzaixian</groupId>
<artifactId>qbzaixian</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- lombok简化实体类开发,需要idea安装lombok的插件哦~~~-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</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>
</project>
2.1.3、书写mybatis的核心配置文件
<?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属性 -->
<properties resource="db.properties"/>
<!--settings 标签 -->
<settings>
<!-- 设置mybatis 的缓存-->
<setting name="cacheEnabled" value="true"/>
<!-- 开启驼峰式命名规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 设置别名 -->
<typeAliases>
<package name="com.qbzaixian.pojo"/>
</typeAliases>
<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文件,书写接口所在的包-->
<package name="com.qbzaixian.mapper"/>
</mappers>
</configuration>
2.1.4、创建数据库
-- 创建数据库
CREATE DATABASE mybatis;
USE mybatis;
DROP TABLE IF EXISTS `tb_item`;
-- 创建商品表
CREATE TABLE `tb_item` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`item_name` VARCHAR(32) NOT NULL COMMENT '商品名称',
`item_price` FLOAT(6,1) NOT NULL COMMENT '商品价格',
`item_detail` TEXT COMMENT '商品描述',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `tb_item` VALUES ('1', 'iPhone 6', '5288.0', '苹果公司新发布的手机产品。');
INSERT INTO `tb_item` VALUES ('2', 'iPhone 6 plus', '6288.0', '苹果公司发布的新大屏手机。');
-- 创建用户表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(100) DEFAULT NULL COMMENT '用户名',
`password` VARCHAR(100) DEFAULT NULL COMMENT '密码',
`name` VARCHAR(100) DEFAULT NULL COMMENT '姓名',
`age` INT(10) DEFAULT NULL COMMENT '年龄',
`sex` TINYINT(1) DEFAULT NULL COMMENT '性别,1男性,2女性',
`birthday` DATE DEFAULT NULL COMMENT '出生日期',
`created` DATETIME DEFAULT NULL COMMENT '创建时间',
`updated` DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`user_name`)
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '30', '1', '1984-08-08', '2022-09-19 16:56:04', '2022-09-21 11:24:59');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '21', '2', '1991-01-01', '2022-09-19 16:56:04', '2022-09-19 16:56:04');
INSERT INTO `tb_user` VALUES ('3', 'wangwu', '123456', '王五', '22', '2', '1989-01-01', '2022-09-19 16:56:04', '2022-09-19 16:56:04');
INSERT INTO `tb_user` VALUES ('4', 'zhangwei', '123456', '张伟', '20', '1', '1988-09-01', '2022-09-19 16:56:04', '2022-09-19 16:56:04');
INSERT INTO `tb_user` VALUES ('5', 'lina', '123456', '李娜', '28', '1', '1985-01-01', '2022-09-19 16:56:04', '2022-09-19 16:56:04');
INSERT INTO `tb_user` VALUES ('6', 'lilei', '123456', '李磊', '23', '1', '1988-08-08', '2022-09-20 11:41:15', '2022-09-20 11:41:15');
-- 订单表
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) NOT NULL,
`order_number` VARCHAR(20) NOT NULL COMMENT '订单号',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_1` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `tb_order` VALUES ('1', '1', '20220921001');
INSERT INTO `tb_order` VALUES ('2', '2', '20220921002');
INSERT INTO `tb_order` VALUES ('3', '1', '20220921003');
-- 创建订单明细表
DROP TABLE IF EXISTS `tb_orderdetail`;
CREATE TABLE `tb_orderdetail` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`order_id` INT(32) DEFAULT NULL COMMENT '订单号',
`item_id` INT(32) DEFAULT NULL COMMENT '商品id',
`total_price` DOUBLE(20,0) DEFAULT NULL COMMENT '商品总价',
`status` INT(11) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`order_id`),
KEY `FK_orderdetail_2` (`item_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`order_id`) REFERENCES `tb_order` (`id`),
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`item_id`) REFERENCES `tb_item` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `tb_orderdetail` VALUES ('1', '1', '1', '5288', '1');
INSERT INTO `tb_orderdetail` VALUES ('2', '1', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('3', '2', '2', '6288', '1');
INSERT INTO `tb_orderdetail` VALUES ('4', '3', '1', '5288', '1');
2.2、注解开发快速入门
2.2.1、编写pojo类
@Data
public class User {
private Long id;
// 用户名
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
}
2.2.2、编写接口
/**
* MyBaits的注解开发
*/
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
public User selectById(Integer id);
}
2.2.3、编写测试类
public class MyBatisTest {
private SqlSessionFactory factory;
@Before
public void init() throws Exception {
//1、获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}
@Test
public void testFindUserById(){
SqlSession session = factory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
System.out.println(user);
}
}
3、注解开发CRUD
mybatis注解开发主要使用的注解有:
- @Select:完成查询sql语句的编写
- @Update:完成修改的SQL语句编写
- @Delete:完成删除的SQL语句编写
- @Insert:完成插入的SQL语句编写
3.1、@Select注解
@Select:标记在方法上,表示该方法对应的SQL是查询语句。
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
public User selectById(Integer id);
@Select("select * from tb_user")
public List<User> selectUsers();
}
@Test
public void testFindUserById(){
SqlSession session = factory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
System.out.println(user);
System.out.println("--------------------");
List<User> users = mapper.selectUsers();
users.forEach(System.out::println);
}
3.2、@Update注解
@Update:表示该方法对应的SQL是更新语句。
@Update("update tb_user set age = #{age} , user_name = #{userName} WHERE id = #{id}")
public void updateUser(User user);
@Test
public void testUpdate(){
SqlSession session = factory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setAge(30);
user.setUserName("小羊苏西");
user.setId(3L);
mapper.updateUser(user);
}
3.3、@Delete注解
@Delete:表示该方法对应的SQL是删除语句。
@Delete("delete from tb_user where id = #{id}")
public void deleteById(Integer id);
@Test
public void testDelete(){
SqlSession session = factory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteById(6);
}
3.4、@Insert注解
@Insert:表示该方法对应的SQL是插入语句。
@Insert("insert into tb_user(user_name,name,age,password,sex) values(#{userName},#{name},#{age},#{password},#{sex})")
public void addUser(User user);
@Test
public void testInsert(){
SqlSession session = factory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setName("zwj");
user.setUserName("张无忌");
user.setAge(20);
user.setPassword("123123");
user.setSex(1);
mapper.addUser(user);
}
4、@Results注解
Results注解主要完成在查询的时候列名和pojo的属性不对应时的映射操作的(类似于Mapper文件中使用resultMap进行配置解决),为了演示这个问题,这里故意修改pojo中的属性与列名不一致。
@Data
public class User2 {
private Long id;
// 用户名:故意修改userName为uName,方便下面测试
private String uName;
// 密码:故意修改password为pwd,方便下面测试
private String pwd;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
}
为了方便,测试,这里在上面演示的CRUD中,对口和测试都是直接复制,在接口和类名后面添加了数字2.
public interface UserMapper2 {
@Select("select * from tb_user where id = #{id}")
public User2 selectById(Integer id);
@Select("select * from tb_user")
public List<User2> selectUsers();
}
public class MyBatisTest2 {
private SqlSessionFactory factory;
@Before
public void init() throws Exception {
//1、获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}
@Test
public void testFindUserById(){
SqlSession session = factory.openSession(true);
UserMapper2 mapper = session.getMapper(UserMapper2.class);
User2 user = mapper.selectById(1);
System.out.println(user);
}
}
查看打印的执行结果:
User2(id=1, uName=null, pwd=null, name=张三, age=30, sex=1, birthday=Wed Aug 08 00:00:00 CST 1984, created=Mon Sep 19 16:56:04 CST 2022, updated=Wed Sep 21 11:24:59 CST 2022)
注意,uName和pwd并没有封装到数据,因为pojo的属性名和列名不对应。无法自动封装。
mybatis提供Results注解解决这个问题
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Results {
/**
* Returns the id of this result map.
*
* @return the id of this result map
*/
String id() default "";
/**
* Returns mapping definitions for property.
*
* @return mapping definitions
*/
Result[] value() default {};
}
其中的id,是给当前这个Results注解起名字,方便在别的地方引用。
其中的Result用来书写具体的列名和属性名的对应关系。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Results.class)
public @interface Result {
/**
* Returns whether id column or not.
*
* @return {@code true} if id column; {@code false} if otherwise
*/
boolean id() default false;
/**
* Return the column name(or column label) to map to this argument.
*
* @return the column name(or column label)
*/
String column() default "";
/**
* Returns the property name for applying this mapping.
*
* @return the property name
*/
String property() default "";
/**
* Return the java type for this argument.
*
* @return the java type
*/
Class<?> javaType() default void.class;
/**
* Return the jdbc type for column that map to this argument.
*
* @return the jdbc type
*/
JdbcType jdbcType() default JdbcType.UNDEFINED;
/**
* Returns the {@link TypeHandler} type for retrieving a column value from result set.
*
* @return the {@link TypeHandler} type
*/
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
/**
* Returns the mapping definition for single relationship.
*
* @return the mapping definition for single relationship
*/
One one() default @One;
/**
* Returns the mapping definition for collection relationship.
*
* @return the mapping definition for collection relationship
*/
Many many() default @Many;
}
Result注解中常用的几个属性解释:
id:当前的列是否为主键
column:对应数据库中的列名
property:对应pojo中的属性名
javaType:对应的属性名的数据类型
jdbcType:对应的数据库列类型
public interface UserMapper2 {
@Select("select * from tb_user where id = #{id}")
@Results(
id = "uDemo",
value = {
@Result(column = "user_name",property = "uName",id = false),
@Result(column = "password",property = "pwd",id = false),
}
)
public User2 selectById(Integer id);
@Select("select * from tb_user")
public List<User2> selectUsers();
}
重新执行测试,会发现对应的属性已经可以自动封装数据
User2(id=1, uName=zhangsan, pwd=123456, name=张三, age=30, sex=1, birthday=Wed Aug 08 00:00:00 CST 1984, created=Mon Sep 19 16:56:04 CST 2022, updated=Wed Sep 21 11:24:59 CST 2022)
在接口中,如果某个方法上已经使用Results定义好映射关系,别的方法上也需要相同的映射关系,可以使用ResultMap注解引用(前提是Results注解必须使用id属性给其命名)
public interface UserMapper2 {
@Select("select * from tb_user where id = #{id}")
@Results(
id = "uDemo",
value = {
@Result(column = "user_name",property = "uName",id = false),
@Result(column = "password",property = "pwd",id = false),
}
)
public User2 selectById(Integer id);
@Select("select * from tb_user")
// 引用上面已经定义好的结果集映射处理注解Results
@ResultMap(value="uDemo")
public List<User2> selectUsers();
}
5、多表操作
实体类
@Data
public class Order {
private Integer id;
private Long userId;
private String orderNumber;
// 添加用户引用
private User user;
// 添加订单明细引用(一个订单对应多个明细,需要使用集合来封装)
private List<Orderdetail> orderdetails;
}
@Data
public class Orderdetail {
private Integer id;
private Double totalPrice;
private Integer status;
// 一个订单明细对应一个商品
private Item item;
}
@Data
public class Item {
private Integer id;
private String itemname;
private Float itemprice;
private String itemdetail;
}
5.1、注解一对一
演示通过订单明细查询对应的商品,订单明细和商品之间是一对一的关系。
在mybatis提供的@Result注解中有:one和many属性,用于完成多表操作注解映射关系配置
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Results.class)
public @interface Result {
// 其他的在上面已经解释过,这里删除省略了...........
One one() default @One;
Many many() default @Many;
}
- one属性表示的一对一的映射配置
- many属性表示一对多的映射配置
public interface OrderdetailMapper {
/**
* 通过订单明细查询对应的商品,订单明细和商品之间是一对一的关系
* 接收订单明细的id
*/
// 主查询:根据订单明细的id查询订单明细,但由于订单明细中关联对应的商品信息
// 需要使用@Results注解配置关联商品信息映射关系
@Select("select * from tb_orderdetail where id = #{id}")
// 配置关联查询
@Results(value = {
// 配置id属性
@Result(column = "id",property = "id",id = true),
@Result(column = "total_price",property = "totalPrice",id = false),
// column配置关联表中的列名
// property配置实体类中对应的属性
@Result(column = "item_id",property = "item",id = false,
// one配置关联的查询
// one注解中的select配置对应的关联查询所在的接口中的方法
// fetchType配置关联查询是即时查询,还是懒加载查询
one=@One(select="com.qbzaixian.mapper.ItemMapper.findById",fetchType = FetchType.EAGER)),
})
public Orderdetail findOrderdetailAndItem(Integer id);
}
public interface ItemMapper {
@Select("select * from tb_item where id = #{id}")
public Item findById(Integer id);
}
@Test
public void testOneToOne(){
SqlSession session = factory.openSession(true);
OrderdetailMapper mapper = session.getMapper(OrderdetailMapper.class);
Orderdetail orderdetail = mapper.findOrderdetailAndItem(1);
System.out.println(orderdetail);
}
在MyBatis注解开发中,FetchType是一个枚举,它定义了关联对象的加载方式,主要有三个值:
LAZY:延迟加载:指示MyBatis在加载主对象时,不会主动加载关联对象,只有当访问关联对象属性时,才会发起加载。
EAGER:急加载:指示MyBatis在每次加载主对象时,会 joins 一并加载关联对象,相当于SQL的子查询。
DEFAULT:默认加载:指示MyBatis按照全局配置来决定使用延迟加载或急加载。
在MyBatis中,默认一对一采用急加载,一对多采用延迟加载。FetchType可以根据需要指定关联对象的加载方式。
5.2、注解一对多
查询订单信息,每个订单都关联着多个订单详情,需要使用@Results注解进行配置
public interface OrderMapper {
@Select("select * from tb_order where id = #{id}")
@Results(value = {
@Result(property = "id",column = "id",id = true),
@Result(property = "orderdetails",column = "id",
many = @Many(select = "com.qbzaixian.mapper.OrderdetailMapper.findOrderdetailAndItem",fetchType = FetchType.LAZY))
})
public Order selectOrderAndOrderDetail(Integer id);
}
@Test
public void testOneToMany(){
SqlSession session = factory.openSession(true);
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.selectOrderAndOrderDetail(1);
System.out.println(order);
}
5.3、注解多对多
上面演示的一对多的查询,由于在订单明细中关联查询了商品信息,其实上面的演示已经完成多对多的查询。
一个订单对应多个商品,每个商品可能被添加到不同的订单中。
6、注解开启缓存
注解开启二级缓存,只需要在类上添加@CacheNamespace,并且将blocking属性设置为true即可。
@CacheNamespace(blocking = true)
public class MyBatisTest2 {
private SqlSessionFactory factory;
}