引入数据库

目前,我们所有数据仍然存储在内存当中,也就是说,每次我们重启应用,我们的数据都会丢失。这节课,我们就来学习如何将数据持久化数据库中,这样,我们就能一直使用使用这些数据了。

我们将会使用Spring Data JPA来操作数据库,Spring Data JPA是一个ORM框架,通过它我们可以很容易的对数据库进行操作,关于Spring Data JPA的细节,可以关注课程Spring Data JPA实战入门训练,这里我们不细说。

第一步仍然是在maven中添加数据库依赖:

org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
runtime

我们使用mysql作为我们的数据库,除了添加依赖外,我们还需要配置mysql的连接字符串以及其账号密码等,这些配置需要写在src/main/resources/application.properties中:

spring.jpa.hibernate.ddl-auto: update
spring.datasource.driverClassName: com.mysql.jdbc.Driver
spring.datasource.url: jdbc:mysql://127.0.0.1/crawler?useUnicode=yes&characterEncoding=UTF-8
spring.datasource.username: root
spring.datasource.password:
spring.datasource.test-on-borrow=true
spring.datasource.validation-query=SELECT 1

注意,我们需要在mysql中创建好数据库,数据库表将由JPA自动为我们生成。

添加@Entity标注

在Spring Data JPA中,操作的对象成为entity,将model变为entity的方式很简单,只需要添加@Entity标注即可。

@Entity
public class Song{
@Id
private String url;
private String title;
private Long commentCount;
...
}
@Entity
public class WebPage{
public enum PageType {
song, playlist, playlists;
}
public enum Status {
crawled, uncrawl;
}
@Id
private String url;
private String title;
@Enumerated(EnumType.STRING)
private PageType type;
@Enumerated(EnumType.STRING)
private Status status;
}

我们可以看到,除了@Entity标注外,还出现了两个标注:

@Id标注,标识该字段为这张表的主键,我们使用url作为主键就好

@Enumerated(EnumType.STRING)表示该字段在java里的类型为枚举类型,JPA会自动根据数据库中的数值转换为对应的枚举值

添加Repository

有了Entity后,我们还需要一个类来对数据库进行操作,在JPA里,这个类称为Repository,我们只需要添加如下对象即可:

public interface SongRepository extends JpaRepository{
}
public interface WebPageRepository extends JpaRepository{
WebPage findTopByStatus(Status status);
}

这样,我们就可以通过Repository来操作数据库了,Repository的实例化Spring将会自动帮我们做好,我们只需要通过@Autowired标注将其注入进来即可。例如,我们要初始化爬虫列表:

@Autowired
private WebPageRepository webPageRepository;
public void initCrawlerList(){
for(int i = 0; i < 43; i++) {
webPageRepository.save(new WebPage("http://music.163.com/discover/playlist/?order=hot&cat=%E5%85%A8%E9%83%A8&limit=35&offset=" + (i * 35), PageType.playlists));
}
}

其中,webPageRepository包含下列方法:

save(WebPage webPage) 当webPage不存在将其添加进数据库中,若存在则更新相应字段,暂时将结果放入缓存中,当当前线程结束后生效

saveAndFlush(WebPage webPage) 当webPage不存在将其添加进数据库中,若存在则更新相应字段,并立即生效

delete(WebPage webPage) 将webPage从数据库中删除

findAll() 获取数据库中的所有webPage

findTopByStatus(Status status) 获取第一条状态为status的webPage

注意,由于我们的环境是多线程环境,为了防止缓存影响我们的结果,我们在添加数据或者更新数据时需要使用saveAndFlush方法。

现在,调用initCrawlerList方法后,数据库中就会添加43条歌单列表的记录,怎么样,是不是很方便。自己来改写一下原来的代码吧,将所有有关爬虫列表以及歌曲列表的操作都变为数据库操作吧。

运行程序

由于我们使用了Spring为我们提供的数据库服务,我们不能使用main函数直接运行我们的程序,需要将Spring环境启动起来。

同时,由于使用了Spring的依赖式注入,我们的MultiCrawlerWithJpa类也需要交给Spring来实例化。在MultiCrawlerWithJpa上添加@Component标注,Spring就会自动帮我们实例化MultiCrawlerWithJpa对象并注入其中的Repository。

@RestController
@RequestMapping("/jpa")
public class JpaCrawlerController{
@Autowired
private MultiCrawlerWithJpa multiCrawler;
@Autowired
private SongRepository songRepository;
@GetMapping("/start")
public String start() throws InterruptedException{
multiCrawler.run();
return "正在爬取中...";
}
@GetMapping("/songs")
public Page songs(Pageable pageable){
return songRepository.findAll(pageable);
}
}

注意,在http://localhost:8080/jpa/songs请求的处理中,我们使用了jpa提供的分页支持,用户可以根据需求添加参数page与size,来改变获取的音乐列表。例如http://localhost:8080/jpa/songs?page=1&size=10将会以10首歌曲为一页,返回第一页的数据。