探索前沿技术
      展示技术风采

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第1部分

企业应用程序中最常见的方案之一是某些后端某处的记录的创建,检索,更新和删除(CRUD!)。

系列章节
第1部分:简介和初始设置:Maven,Spring和JPA /后端(数据库)
第2部分:中间层:使用REST服务公开我们的数据
第3部分:前端 – 初步实施
第4部分:前端 – 网格功能和CRUD(创建,读取,更新和删除)

介绍
我们的应用程序将使用以下技术:

后端:数据库的H2(后面的Oracle,可选,章节)
中间层:Java,Spring,Hibernate
前端:ag-Grid,Angular 4.x

我们的应用程序将成为一个报告工具,可以查看过去的奥运会结果。 用户将能够看到2000年至2012年奥运会的历史性成果(总共7场比赛),并且能够看到谁赢得了一年中最多的奖牌,谁赢得最多,哪个国家 – 平均来说 – 最成功的,等等。

为了专注于所讨论的概念,应用程序非常简单 – 我们将使用传统的3层模型实现:

或者,您可以按如下方式细分:

初始设置
先决条件
为了完成这一系列,需要使用许多工具。 请参阅他们的文档,了解如何为特定操作系统下载和安装它。

后续部分将假定已安装这些工具并可供使用。

  • Java 8
  • Maven 3
  • NodeJS 6 (includes npm)
  • Git
springboot实战开发

springboot实战开发

 

运行应用程序
您可以通过以下两种方式之一启动应用程序:

在IDE中
如果您使用的是IDE,则可以运行src/main/java/com/fundodoo/blog/aggrid/AgGridApplication.java – 这将启动该应用程序。

通过Maven
mvn spring-boot:运行
在任何一种情况下,应用程序都会启动,但在这个阶段实际上并没有做太多。

Data Model

springboot实战开发

springboot实战开发

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:33
 * @since 1.0.0
 */
@Entity
@Cacheable(false)
@Data
@ToString
@EqualsAndHashCode
public class Athlete {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version()
    private Long version = 0L;

    private String name;

    @OneToOne()
    private Country country;

    @JsonManagedReference
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "athlete", orphanRemoval = true)
    private List<Result> results = new ArrayList<>();

    public Athlete() {
    }

    public Athlete(String name, Country country, List<Result> results) {
        this.name = name;
        this.country = country;
        this.results = results;
    }
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:37
 * @since 1.0.0
 */

@Entity
@Data
@ToString
@EqualsAndHashCode
public class Country {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public Country() {
    }

    public Country(String name) {
        this.name = name;
    }


}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:38
 * @since 1.0.0
 */
@Entity
@Data
@ToString
@EqualsAndHashCode
public class Result {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version()
    private Long version = 0L;

    @JsonBackReference
    @ManyToOne(cascade = CascadeType.ALL)
    private Athlete athlete;

    private int age;
    private int year;
    private String date;
    private int gold;
    private int silver;
    private int bronze;

    @OneToOne()
    private Sport sport;

    public Result() {
    }

    public Result(Sport sport, int age, int year, String date, int gold, int silver, int bronze) {
        this.sport = sport;
        this.age = age;
        this.year = year;
        this.date = date;
        this.gold = gold;
        this.silver = silver;
        this.bronze = bronze;
    }
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:40
 * @since 1.0.0
 */
@Entity
@Data
@ToString
@EqualsAndHashCode
public class Sport {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    public Sport() {
    }

    public Sport(String name) {
        this.name = name;
    }
}

通过上面的配置,Hibernate将为我们创建以下物理模型:

springboot实战开发

springboot实战开发

到目前为止,我们有实体及其映射 – 这很好,但为了有用,我们需要一个能够执行数据读/写/删除的工具。 Spring通过提供我们可以扩展的CrudRepository接口使我们更容易。 只需很少的努力,我们就可以获得大量功能 – 这可以节省大量时间。

以下是我们将在此应用程序中使用的4个Repository   一个用于上述每个实体:

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:45
 * @since 1.0.0
 */
public interface AthleteRepository extends CrudRepository<Athlete, Long> {
    Athlete findByName(String name);
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:46
 * @since 1.0.0
 */
public interface CountryRepository extends CrudRepository<Country, Long> {
    Country findByName(String name);
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:46
 * @since 1.0.0
 */
public interface ResultRepository extends CrudRepository<Result, Long> {
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午7:47
 * @since 1.0.0
 */
public interface SportRepository extends CrudRepository<Sport, Long> {
    Sport findByName(String name);
}

H2控制台
我们可以在H2中查看为我们生成的模型,但打开H2控制台。 默认情况下禁用此功能 – 我们需要更新/src/main/resources/application.yaml以启用它:

spring:
  h2:
    console:
      enabled: true

然后我们可以导航到http://localhost:8080/h2-console/并查看我们拥有的内容。

确保JDBC URL字段设置为jdbc:h2:mem:testdb。 H2将缓存您可能使用的任何先前的URL,但是我们在这里使用的内存H2将是jdbc:h2:mem:testdb

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 - 第1部分

测试数据 – 引导
当我发现我错误地插入重复的运动员时,此部分已更新。

在编写代码之前编写测试,并经常编写它们!

为了将我们拥有的数据(src/main/resources/olympicWinners.csv)存入H2数据库,我们将利用Spring的ApplicationListener <ContextRefreshedEvent>工具来加载测试数据并将其保存到数据库中。

我们将使用名为Jackson的第三方库将CSV转换为Java POJO,因此将其添加到依赖项块中的Maven pom.xml文件中:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
    <version>2.9.1</version>
    <scope>test</scope>
</dependency>

请注意,我们只使用此库进行本地测试,因此它具有测试范围。

/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午8:18
 * @since 1.0.0
 */
@Component
public class Bootstrap implements ApplicationListener<ContextRefreshedEvent> {

    private CountryRepository countryRepository;
    private SportRepository sportRepository;
    private AthleteRepository athleteRepository;

    public Bootstrap(CountryRepository countryRepository,
                     SportRepository sportRepository,
                     AthleteRepository athleteRepository) {
        this.countryRepository = countryRepository;
        this.sportRepository = sportRepository;
        this.athleteRepository = athleteRepository;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        List<RawOlympicWinnerRecord> olympicWinnerRecords = loadOlympicWinnersRecords();

        Map<String, Country> countryNameToCountry = new HashMap<>();
        Map<String, Sport> sportNameToSport = new HashMap<>();
        buildMapForCountryAndSport(olympicWinnerRecords, countryNameToCountry, sportNameToSport);

        Map<String, List<Result>> athleteNameToResults = new HashMap<>();
        buildMapForResults(olympicWinnerRecords, sportNameToSport, athleteNameToResults);

        Map<String, Athlete> athletes = new HashMap<>();
        for (RawOlympicWinnerRecord olympicWinnerRecord : olympicWinnerRecords) {
            List<Result> resultsForAthlete = athleteNameToResults.get(olympicWinnerRecord.athlete);

            athletes.computeIfAbsent(olympicWinnerRecord.athlete, (key) -> {
                return new Athlete(olympicWinnerRecord.athlete,
                        countryNameToCountry.get(olympicWinnerRecord.country),
                        resultsForAthlete);
            });

            Athlete athlete = athletes.get(olympicWinnerRecord.athlete);

            for (Result resultForAthlete : resultsForAthlete) {
                resultForAthlete.setAthlete(athlete);
            }
        }

        // we now have the test data - save it to the database
        this.countryRepository.saveAll(countryNameToCountry.values());
        this.sportRepository.saveAll(sportNameToSport.values());
        this.athleteRepository.saveAll(athletes.values());
    }

    private void buildMapForResults(List<RawOlympicWinnerRecord> olympicWinnerRecords, Map<String, Sport> sportNameToSport, Map<String, List<Result>> athleteNameToResults) {
        for (RawOlympicWinnerRecord olympicWinnerRecord : olympicWinnerRecords) {
            athleteNameToResults.computeIfAbsent(olympicWinnerRecord.athlete, k -> new ArrayList<>()).add(
                    new Result(sportNameToSport.get(olympicWinnerRecord.sport),
                            olympicWinnerRecord.age,
                            olympicWinnerRecord.year,
                            olympicWinnerRecord.date,
                            olympicWinnerRecord.gold,
                            olympicWinnerRecord.silver,
                            olympicWinnerRecord.bronze
                    )
            );
        }
    }

    private void buildMapForCountryAndSport(List<RawOlympicWinnerRecord> olympicWinnerRecords, Map<String, Country> countryNameToCountry, Map<String, Sport> sportNameToSport) {
        for (RawOlympicWinnerRecord olympicWinnerRecord : olympicWinnerRecords) {
            countryNameToCountry.putIfAbsent(olympicWinnerRecord.country, new Country(olympicWinnerRecord.country));
            sportNameToSport.putIfAbsent(olympicWinnerRecord.sport, new Sport(olympicWinnerRecord.sport));
        }
    }

    private List<RawOlympicWinnerRecord> loadOlympicWinnersRecords() {
        CsvLoader csvLoader = new CsvLoader();
        return csvLoader.loadObjectList(RawOlympicWinnerRecord.class, "./olympicWinners.csv");
    }
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午8:20
 * @since 1.0.0
 */
class CsvLoader {
    <T> List<T> loadObjectList(Class<T> type, String fileName) {
        try {
            CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
            CsvMapper mapper = new CsvMapper();
            File file = new ClassPathResource(fileName).getFile();
            MappingIterator<T> readValues =
                    mapper.reader(type).with(bootstrapSchema).readValues(file);
            return readValues.readAll();
        } catch (Exception e) {
            System.out.println("Error occurred while loading object list from file " + fileName);
            e.printStackTrace();

            return Collections.emptyList();
        }
    }
}
/**
 *
 * @author 醉探索戈壁
 * @create 2018/7/10 下午8:20
 * @since 1.0.0
 */
public class RawOlympicWinnerRecord {
    public String athlete;
    public int age;
    public String country;
    public int year;
    public String date;
    public String sport;
    public int gold;
    public int silver;
    public int bronze;
    public int total;
}

启动时,src/main/resources/olympicWinners.csv中的数据将被加载,解析并插入数据库,供我们查询。

让我们启动应用程序然后启动H2控制台来测试我们拥有的内容。 如果我们在H2控制台中运行以下查询:

select a.name, c.name, r.age, c.name, r.year, r.date,s.name,r.gold,r.silver,r.bronze
from athlete a, country c, result r, sport s
where a.country_id = c.id
and r.athlete_id = a.id
and r.sport_id = s.idand r.sport_id = s.id

概要
这个系列的第一部分必然会有一些数字。 值得注意的是,如果您只是运行上面的命令(并创建列出的Java类),您将使用完全成熟的CRUD系统启动并运行 – 所有这些都在不超过30分钟内完成,这非常了不起。

为了访问我们刚刚实现的CRUD功能,我们需要将其提供给前端 – 我们将在本系列的下一部分开始介绍它!

SpringBoot实战: 集成ag-Grid构建CRUD应用程序 - 第1部分

springboot实战开发: 集成ag-Grid构建CRUD应用程序 – 第1部分完结!

×

感谢您的支持,我们会一直保持!

扫码支持
请土豪扫码随意打赏

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

赞(0) 打赏
未经允许不得转载:醉探索戈壁 » SpringBoot实战: 集成ag-Grid构建CRUD应用程序 – 第1部分
分享到: 更多 (0)
标签:

给戈壁浇点水

支付宝扫一扫打赏

微信扫一扫打赏