4. ORM 操作 MySQL
4. ORM 操作 MySQL
使用 MyBatis 框架操作数据, 在 SpringBoot 框架集成 MyBatis
使用步骤:
mybatis 起步依赖: 完成 mybatis 对象自动配置, 对象放在容器中
pom.xml 指定把 src/main/java 目录中的 xml 文件包含到 classpath 中
创建实体类 Student
创建 Dao 接口 StudentDao, 创建一个查询学生的方法
创建 Dao 接口对应的 Mapper 文件, xml 文件, 写 sql 语句
创建 Service 层对象, 创建 StudentService 接口和他的实现类. 去 dao 对象的方法.完成数据库的操作
创建 Controller 对象, 访问 Service.
写 application.properties 文件, 配置数据库的连接信息.
准备工作
数据表:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '李四', 20);
INSERT INTO `student` VALUES (2, '张三', 28);
INSERT INTO `student` VALUES (3, '周鑫', 30);
SET FOREIGN_KEY_CHECKS = 1;
起步依赖:
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
实体类:
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@Component
public class Student {
private Integer id;
private String name;
private int age;
}
StudentDao 接口:
public interface StudentDao {
Student selectById(@Param("stuId") Integer id);
}
StudentDao.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">
<!-- namespace属性是名称空间,必须唯一 -->
<mapper namespace="com.example.SpringBoot.dao.StudentDao">
<!--
resultMap标签:映射实体与表
type属性:表示实体全路径名
id属性:为实体与表的映射取一个任意的唯一的名字
-->
<resultMap type="com.example.SpringBoot.bean.Student" id="student">
<!--
id标签:映射主键属性
result标签:映射非主键属性
property属性:实体的属性名
column属性:表的字段名
-->
<result property="name" column="name"/>
<result property="age" column="age"/>
</resultMap>
<select id="selectById" resultMap="student">
select * from db01.student where id= #{stuId};
</select>
</mapper>
service 接口:
public interface StudentService {
Student queryStudent(Integer id);
}
service 实现类:
@Service
public class StudentServiceImpl implements StudentService {
@Resource
private StudentDao studentDao;
@Override
public Student queryStudent(Integer id) {
return studentDao.selectById(id);
}
}
application.properties
#连接数据库
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db01?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
pom.xml 中指定资源文件
<build>
<!--resources插件-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
完成 mybatis 对象自动配置
第一种方式: @Mapper
@Mapper
: 放在 dao 接口的上面, 每个 dao 接口都需要使用这个注解, 接口多的时候很不方便.
/**
* @Mapper: 告诉MyBatis这是dao接口, 创建此接口的代理对象.
* 位置:在类的上面
*/
@Mapper
public interface StudentDao {
Student selectById(@Param("stuId") Integer id);
}
第二种方式 @MapperScan
在任意配置类中使用 @MapperScan
包扫描, 无需逐个添加 @Mapper
.
/**
* @MapperScan: 找到Dao接口和Mapper文件
* basePackages:Dao接口所在的包名
* basePackages可省略
*/
@SpringBootApplication
@MapperScan("com.example.SpringBoot.dao")
// // @MapperScan({"com.example.SpringBoot.dao", "com.example.SpringBoot"})
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
dao 接口:
public interface StudentDao {
Student selectById(@Param("stuId") Integer id);
}
@MapperScan 源码:
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
String lazyInitialization() default "";
String defaultScope() default "";
}
第三种方式: Mapper 文件和 Dao 接口分开管理
在 resources 目录中创建子目录 (自定义的), 例如 mapper
把 mapper 文件放到 mapper 目录中
在 application.properties 文件中, 指定 mapper 文件的目录
把 Mapper 文件放在 resources 目录下
添加配置:
#指定mapper文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
#指定mybatis的日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在 pom.xml 中指定 把 resources 目录中的文件, 编译到目标目录中
<!--resources插件-->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
事务
Spring 框架中的事务:
管理事务的对象: 事务管理器(接口, 接口有很多的实现类)
例如:使用 Jdbc 或 mybatis 访问数据库, 使用的事务管理器: DataSourceTransactionManager
声明式事务: 在 xml 配置文件或者使用注解说明事务控制的内容
控制事务: 隔离级别, 传播行为, 超时时间
事务处理方式:
Spring 框架中的
@Transactional
aspectj 框架可以在 xml 配置文件中, 声明事务控制的内容
SpringBoot 中使用事务: 上面的两种方式都可以.
在业务方法上面加入
@Transactional
, 加入注解后, 方法有事务功能了.明确的在主启动类的上面加入
@EnableTransactionManagement
(不是必须).
业务方法:
/**
* @Transactional: 表示方法的有事务支持
* 默认:使用库的隔离级别, REQUIRED 传播行为; 超时时间 -1
* 抛出运行时异常, 回滚事务
*/
@Transactional
@Override
public int addStudent(Student student) {
System.out.println("业务方法执行: addStudent");
int addStudent = studentMapper.insert(student);
System.out.println("执行SQL语句");
// 抛出异常, 事务回滚
int error = 10 / 0;
return addStudent;
}
启动类:
@SpringBootApplication
@EnableTransactionManagement // 启用事务管理器, (不是必须)
@MapperScan("com.example.SpringBoot.mapper")
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
测试时会抛出 java.lang.ArithmeticException: / by zero
异常, 将 int error = 10 / 0;
取消掉后重新测试数据库能正常添加数据, 但主键因为设有自增所以结果会跳过前面一个数字, 例如直接从 3 跳到 5, 中间的 4 就是因为异常回滚的数据.