4. ORM 操作 MySQL

空~2022年9月7日
  • SpringBoot
大约 5 分钟

4. ORM 操作 MySQL

使用 MyBatis 框架操作数据, 在 SpringBoot 框架集成 MyBatis

使用步骤:

  1. mybatis 起步依赖: 完成 mybatis 对象自动配置, 对象放在容器中

  2. pom.xml 指定把 src/main/java 目录中的 xml 文件包含到 classpath 中

  3. 创建实体类 Student

  4. 创建 Dao 接口 StudentDao, 创建一个查询学生的方法

  5. 创建 Dao 接口对应的 Mapper 文件, xml 文件, 写 sql 语句

  6. 创建 Service 层对象, 创建 StudentService 接口和他的实现类. 去 dao 对象的方法.完成数据库的操作

  7. 创建 Controller 对象, 访问 Service.

  8. 写 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 接口分开管理

  1. 在 resources 目录中创建子目录 (自定义的), 例如 mapper

  2. 把 mapper 文件放到 mapper 目录中

  3. 在 application.properties 文件中, 指定 mapper 文件的目录

把 Mapper 文件放在 resources 目录下

image-20220921101639713

添加配置:

#指定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 框架中的事务:

  1. 管理事务的对象: 事务管理器(接口, 接口有很多的实现类)

    例如:使用 Jdbc 或 mybatis 访问数据库, 使用的事务管理器: DataSourceTransactionManager

  2. 声明式事务: 在 xml 配置文件或者使用注解说明事务控制的内容

    控制事务: 隔离级别, 传播行为, 超时时间

  3. 事务处理方式:

    1. Spring 框架中的 @Transactional

    2. aspectj 框架可以在 xml 配置文件中, 声明事务控制的内容

SpringBoot 中使用事务: 上面的两种方式都可以.

  1. 在业务方法上面加入 @Transactional, 加入注解后, 方法有事务功能了.

  2. 明确的在主启动类的上面加入 @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 就是因为异常回滚的数据.