Java 之旅

中级19 - Spring生态系统

2020-06-02  本文已影响0人  晓风残月1994

俗称Spring全家桶。为什么大家都要用它?因为它好用啊。

1. Spring 中 Bean 配置与 Spring+MyBatis 实战

先说个宏观印象,每个需要 @Autowired 的 Bean 之所以是个 Bean,一定是某种方式通知了 Spring 这是个 Bean,一般要么通过配置文件(查看上一篇笔记),要么在 Bean 的类上添加五花八门的注解,之所以五花八门,自然是希望传达更多的角色分工的信息,而他们共同的最重要的信息仍然是:告诉 Spring 这里的类是一个 Bean。

一般分为三层:
(HTTP)

(数据库)

接下来,做一个 Rank 排行榜的页面。

在上一篇的 Spring boot Web 基础上继续增加 Mabatis 相关依赖,H2 的依赖。
https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

pom.xml:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-spring-boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.200</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>6.4.3</version>
                <configuration>
                    <url>jdbc:h2:file:${project.basedir}/target/test</url>
                    <user>root</user>
                    <password>root</password>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

增加 src/java/main/resources/application.properties 文件:

spring.datasource.url=jdbc:h2:file:/Users/peng/IdeaProjects/tmp/my-first-spring/target/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.h2.Driver
mybatis.config-location=classpath:db/mybatis/config.xml

引入 Flyway 插件,方便数据库快速迁移。

<plugin>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-maven-plugin</artifactId>
  <version>6.4.3</version>
  <configuration>
    <url>jdbc:h2:file:${project.basedir}/target/test</url>
    <user>root</user>
    <password>root</password>
  </configuration>
</plugin>

然后resources/db/migration/V1__CreateTables.sql 创建两张表,一张用户表(User),一张比赛表(Match),联表查询即可得到 Rank 排行榜。

create table user
(
    id   bigint primary key auto_increment,
    name varchar(100)
);

create table match
(
    id      bigint primary key auto_increment,
    user_id bigint,
    score   int
);

insert into user(id, name)
values (1, 'AAA');
insert into user(id, name)
values (2, 'BBB');
insert into user(id, name)
values (3, 'CCC');

insert into match(id, user_id, score)
values (1, 1, 1000);
insert into match(id, user_id, score)
values (2, 1, 2000);
insert into match(id, user_id, score)
values (3, 2, 500);
insert into match(id, user_id, score)
values (4, 3, 300);

然后运行 mvn flyway:migrate ,会自动创建一个 H2数据库,并且执行刚才的 sql。

在 Spring 的 application.properties 文件指定 Mybatis 配置文件的路径,并创建 Mybatis 的配置文件,以及基于注解的 mapper 和基于 xml 的 mapper:

<?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>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <mappers>
        <mapper resource="db/mybatis/MyMapper.xml"/>
        <mapper class="hello.dao.UserMapper"/>
    </mappers>
</configuration>
// 这里的 UserMapper 只在这里测试连接的时候用到了,后面都没用到
package hello.dao;

import hello.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    @Select("select * from user where id = #{id}")
    User getUserById(@Param("id") Integer id);
}

下面演示使用基于注解的 mapper,把 UserMapper 引入进来,然后使用 @Autowired 自动进行装配(IDEA可能提示找不到UserMapper bean,alt+enter 进行忽略,因为动态运行,静态检查难免无力)(注意,现在为了方便,暂时没有引入 Service 层):

package hello;

import hello.dao.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/")
    @ResponseBody
    public Object search() {
        return userMapper.getUserById(1);
    }
}

现在访问 http://localhost:8080/ 已经有返回了。

增加 RankItem、RankDao、RankService,代码解释往下看:

package hello.entity;

public class RankItem {
    private int score;
    private User user;

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
package hello.dao;

import hello.entity.RankItem;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RankDao {

    @Autowired
    private SqlSession sqlSession;

    public List<RankItem> getRank() {
        return sqlSession.selectList("MyMapper.selectRank");
    }
}
package hello.service;

import hello.dao.RankDao;
import hello.entity.RankItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RankService {
    @Autowired
    RankDao rankDao;

    public List<RankItem> getRank() {
        return rankDao.getRank();
    }
}

Spring 如何知道这是一个 Bean?传统方式是配置 xml(上一篇中有提到),而更方便的方法是,假如这是一个 Service,那么直接使用 @Service 注解告诉 Spring 即可,然后 Spring 进一步根据 Bean 中的 @Autowired 注解来对 Bean 进行识别、自动装配、注入(Spring Bean 默认都是单例模式)。

如果想配置非常复杂的 Bean,可以额外引入配置,使用 @Configuration,比单纯使用 @Service 更灵活复杂(下一篇再细说,本篇先简单使用 @Service 即可)。

接下来处理一下最重要的数据库操作,选出 用户id、用户总分、用户名(并按照用户总分降序排列)数据。
由于比较复杂,所以使用 Mybatis 的 xml mapper,并使用了 resultMap 映射出存在嵌套对象的 RankItem 对象(其代表最终的一行排名信息)。

<?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="MyMapper">
    <select id="selectRank" resultMap="rankItem">
        select t.user_id, t.score_sum, user.name as user_name
        from
        (
        select user_id, sum(score) as score_sum from match
            group by user_id
            order by score_sum desc
        ) t
         inner join user on t.user_id = user.id;
    </select>

    <resultMap id="rankItem" type="hello.entity.RankItem">
        <result property="score" column="score_sum"/>
        <association property="user" javaType="hello.entity.User">
            <result property="id" column="user_id"/>
            <result property="name" column="user_name"/>
        </association>
    </resultMap>
</mapper>

接下来问题来了,之前学习 Mybatis 基于 xml mapper 时,需要用 SqlSessionFactory 创建出一个 SqlSession(sql 会话),然后执行相应的操作类型,以及具体的 xml sql mapper,例如:

// 仅供演示之前做法
public List<Order> getInnerJoinOrders() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        return session.selectList("MyMapper.getInnerJoinOrders");
    }
}

现在既然使用了 Spring,也依赖了基于 Spring 的 Mybatis 包,那么只需要在需要一个 SqlSession 的时候使用@Autowired 注解即可,由 Spring 启动后负责创建实例。
另外,目前为止,看似 RankService 比较尴尬,该 Service 只是作为了 Dao 和 Controller 之间“多余”的一层桥梁,这里是完全可以直接在 Controller 中引用 Dao 的。但别忘了,以后业务逻辑逐渐复杂时,就体现出 Service 层的必要性了。

把前面的 HelloController 改为以下,验证以下 Controller、Service、Dao 三者之间递进的分层关系:

package hello;

import hello.service.RankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private RankService rankService;

    @RequestMapping("/")
    @ResponseBody
    public Object search() {
        return rankService.getRank();
    }
}

如果没写错的话,再次启动 Spring,访问 http://localhost:8080/ 可以看到返回的 RankItem 列表:

[
    {
        "score": 3000,
        "user": {
            "id": 1,
            "name": "AAA"
        }
    },
    {
        "score": 500,
        "user": {
            "id": 2,
            "name": "BBB"
        }
    },
    {
        "score": 300,
        "user": {
            "id": 3,
            "name": "CCC"
        }
    }
]
上一篇 下一篇

猜你喜欢

热点阅读