個(gè)人積累,請(qǐng)勿私自轉(zhuǎn)載,轉(zhuǎn)載前請(qǐng)聯(lián)系
代碼及文章資源https://github.com/jedyang/DayDayUp/tree/master/java/springboot
基于SpringBootCookBook的讀書筆記,重在個(gè)人理解和實(shí)踐,而非翻譯。
入門
在現(xiàn)代快節(jié)奏的軟件開發(fā)中,快速開發(fā)和原型設(shè)計(jì)變得越來越重要。如果你是使用JVM平臺(tái)語言(不只是java哦),那springboot是加快開發(fā)速度的利器。 下面將如何將工程boot化。
使用springboot的template和starter
springboot包含了40多個(gè)不同的starter模塊,為各種功能提供了即時(shí)可用的library。如數(shù)據(jù)庫連接,監(jiān)控,web服務(wù)等等。 不會(huì)一個(gè)一個(gè)介紹,看幾個(gè)最重要的。
去https://start.spring.io/生成一個(gè)最簡(jiǎn)單的springboot應(yīng)用。
在這個(gè)頁面上可以選擇需要的dependency。在生成的工程中會(huì)有對(duì)應(yīng)的starter。比如,我選了mysql,mybatis。生成的pom中有:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
那么,springboot的starter究竟是什么?
springboot的目標(biāo)是簡(jiǎn)化系統(tǒng)的構(gòu)建。starter就是啟動(dòng)一個(gè)特定工程需要的一系列依賴的集合。
每個(gè)starter都有一個(gè)特殊的文件spring.provides,如starter-test,https://github.com/spring-projects/spring-boot/blob/master/spring-boot-starters/spring-boot-starter-test/src/main/resources/META-INF/spring.provides
可以看到它集成了provides: spring-test,spring-boot,junit,mockito,hamcrest-library,assertj,jsonassert,json-path。
這樣我們不在需要手動(dòng)添加這些依賴。
常用的starter如下:
- spring-boot-starter: 這是核心Spring Boot starter,提供了大部分基礎(chǔ)功能,其他starter都依賴于它,因此沒有必要顯式定義它。
- spring-boot-starter-actuator:主要提供監(jiān)控、管理和審查應(yīng)用程序的功能。
- spring-boot-starter-jdbc:該starter提供對(duì)JDBC操作的支持,包括連接數(shù)據(jù)庫、操作數(shù)據(jù)庫,以及管理數(shù)據(jù)庫連接等等。
- spring-boot-starter-data-jpa:JPA starter提供使用Java Persistence API(例如Hibernate等)的依賴庫。
- spring-boot-starter-data-*:提供對(duì)MongoDB、Data-Rest或者Solr的支持。
- spring-boot-starter-security:提供所有Spring-security的依賴庫。
- spring-boot-starter-test:這個(gè)starter包括了spring-test依賴以及其他測(cè)試框架,例如JUnit和Mockito等等。
- spring-boot-starter-web:該starter包括web應(yīng)用程序的依賴庫。
動(dòng)手建一個(gè)demo工程
一個(gè)簡(jiǎn)單的圖書管理系統(tǒng)。在spring的創(chuàng)建頁面上。
- Group設(shè)置為:org.test;
- Artifact設(shè)置為:bookpub;
- Name設(shè)置為:BookPub;
- Package Name設(shè)置為:org.test.bookpub;
- Packaging代表打包方式,我們選jar;
- Spring Boot Version選擇最新的1.3.0;
- 創(chuàng)建Maven工程,當(dāng)然,對(duì)Gradle比較熟悉的同學(xué)可以選擇Gradle工程。
- 依賴添加H2,JDBC,JPA
- 點(diǎn)擊“Generate Project”下載工程包。
打開pom文件,可以看到對(duì)應(yīng)的starter。
然后看一下主代碼。
@SpringBootApplication
public class BookPubApplication {
public static void main(String[] args) {
SpringApplication.run(BookPubApplication.class, args);
}
}
就是這么少,沒有配置文件,也沒有數(shù)據(jù)庫配置。但是可以運(yùn)行,實(shí)現(xiàn)這個(gè)魔法的是注解@SpringBootApplication。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
注意,@SpringBootConfiguration是被@Configuration注解,@Configuration表示被注解的類包含spring配置定義。
@ComponentScan,包掃描。默認(rèn)以該類所在的包為根路徑掃描。
@EnableAutoConfiguration,這個(gè)是spring-boot新引入的注解作用是自動(dòng)配置。
在main方法中SpringApplication.run(BookPubApplication.class, args);讀取BookPubApplication的注解,并初始化context。
啟動(dòng)看下,使用mvn spring-boot:run在對(duì)應(yīng)目錄啟動(dòng),觀察啟動(dòng)成功。
順便安利下H2數(shù)據(jù)庫,是一個(gè)內(nèi)存數(shù)據(jù)庫,也支持持久化,個(gè)人感覺很好用。請(qǐng)搜索相關(guān)文檔。
使用Command-line runners
讓我們加點(diǎn)代碼.
新增一個(gè)類。
package org.test.bookpub;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.CommandLineRunner;
/**
* Created by yangyunsheng on 2017/7/12.
*/
public class StartupRunner implements CommandLineRunner{
Log logger = LogFactory.getLog(getClass());
@Override
public void run(String... strings) throws Exception {
logger.info("hello");
}
}
在BookPubApplication中增加bean注入:
@Bean
public StartupRunner schedulerRunner() {
return new StartupRunner();
}
可以看到成功的輸出:
2017-07-12 19:10:08.141 INFO 11032 --- [ main] org.test.bookpub.StartupRunner : hello
CommandLineRunner適用于那種在程序啟動(dòng)時(shí)只執(zhí)行一次的任務(wù),如各種資源的加載。
springboot會(huì)掃描實(shí)現(xiàn)CommandLineRunner的類,執(zhí)行其中的run方法。
run方法的參數(shù)是啟動(dòng)函數(shù)(如main函數(shù))傳入的。
另外,可以使用@Order注解或者實(shí)現(xiàn)Order接口,來控制多個(gè)Runner的執(zhí)行順序。通過設(shè)置value的值,值越小,執(zhí)行越早。
注意:因?yàn)镃ommandLineRunner是執(zhí)行在啟動(dòng)階段,一旦報(bào)錯(cuò)將阻斷整個(gè)應(yīng)用,所以一定記得用try-catch處理異常。
建立數(shù)據(jù)庫連接
修改一下代碼:
@Order(value = 1)
public class StartupRunner implements CommandLineRunner{
protected final Log logger = LogFactory.getLog(getClass());
@Autowired
private DataSource ds;
@Override
public void run(String... strings) throws Exception {
logger.info("dataSource:" + ds.toString());
}
}
執(zhí)行,看日志:
2017-07-13 09:32:49.464 INFO 8132 --- [ main] org.test.bookpub.StartupRunner : dataSource:org.apache.tomcat.jdbc.pool.DataSource@f107c50{ConnectionPool[defaultAutoCommit=null; defaultReadOnly=null; defaultTransactionIsolation=-1; defaultCatalog=null; driverClassName=org.h2.Driver; .... }
可以看到,因?yàn)镠2是一種內(nèi)存數(shù)據(jù)庫,我們僅需要引入H2的依賴,執(zhí)行時(shí)如果沒有H2實(shí)例,程序會(huì)自動(dòng)創(chuàng)建一個(gè)H2數(shù)據(jù)庫。 但是這樣每次結(jié)束應(yīng)用,數(shù)據(jù)都會(huì)丟失。幸運(yùn)的是,H2支持?jǐn)?shù)據(jù)持久化,我們可以將數(shù)據(jù)保存到文件里。繼續(xù)修改代碼:
在resources目錄下的application.properties中增加配置:
spring.datasource.url = jdbc:h2:~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username = sa
spring.datasource.password =
再次執(zhí)行,會(huì)在你的用戶目錄下生產(chǎn)test.mv.db數(shù)據(jù)庫文件。
連接mysql的代碼:
spring.datasource.driver-class-name: com.mysql.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/springbootcookbook
spring.datasource.username: root
spring.datasource.password:
如果使用hibernate自動(dòng)創(chuàng)建schemal,再加一個(gè):
spring.jpa.hibernate.ddl-auto=create-drop
這里多說一句: ddl-auto的參數(shù)有:validate | update | create | create-drop
其實(shí)這個(gè)ddl-auto參數(shù)的作用主要用于:自動(dòng)創(chuàng)建|更新|驗(yàn)證數(shù)據(jù)庫表結(jié)構(gòu)。如果不是此方面的需求建議set value="none"。
- create:每次加載hibernate時(shí)都會(huì)刪除上一次的生成的表,然后根據(jù)你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執(zhí)行,這就是導(dǎo)致數(shù)據(jù)庫表數(shù)據(jù)丟失的一個(gè)重要原因。
- create-drop :每次加載hibernate時(shí)根據(jù)model類生成表,但是sessionFactory一關(guān)閉,表就自動(dòng)刪除。生產(chǎn)環(huán)境不要用。
- update:最常用的屬性,第一次加載hibernate時(shí)根據(jù)model類會(huì)自動(dòng)建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫),以后加載hibernate時(shí)根據(jù) model類自動(dòng)更新表結(jié)構(gòu),即使表結(jié)構(gòu)改變了但表中的行仍然存在不會(huì)刪除以前的行。要注意的是當(dāng)部署到服務(wù)器后,表結(jié)構(gòu)是不會(huì)被馬上建立起來的,是要等 應(yīng)用第一次運(yùn)行起來后才會(huì)。
- validate :每次加載hibernate時(shí),驗(yàn)證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),只會(huì)和數(shù)據(jù)庫中的表進(jìn)行比較,不會(huì)創(chuàng)建新表,但是會(huì)插入新值。
再說點(diǎn)“廢話”:
當(dāng)我們把hibernate.hbm2ddl.auto=create時(shí)hibernate先用hbm2ddl來生成數(shù)據(jù)庫schema。
當(dāng)我們把hibernate.cfg.xml文件中hbm2ddl屬性注釋掉,這樣我們就取消了在啟動(dòng)時(shí)用hbm2ddl來生成數(shù)據(jù)庫schema。通常 只有在不斷重復(fù)進(jìn)行單元測(cè)試的時(shí)候才需要打開它,但再次運(yùn)行hbm2ddl會(huì)把你保存的一切都刪除掉(drop)---- create配置的含義是:“在創(chuàng)建SessionFactory的時(shí)候,從scema中drop掉所以的表,再重新創(chuàng)建它們”。
注意,很多Hibernate新手在這一步會(huì)失敗,我們不時(shí)看到關(guān)于Table not found錯(cuò)誤信息的提問。但是,只要你根據(jù)上面描述的步驟來執(zhí)行,就不會(huì)有這個(gè)問題,因?yàn)閔bm2ddl會(huì)在第一次運(yùn)行的時(shí)候創(chuàng)建數(shù)據(jù)庫schema, 后續(xù)的應(yīng)用程序重啟后還能繼續(xù)使用這個(gè)schema。假若你修改了映射,或者修改了數(shù)據(jù)庫schema,你必須把hbm2ddl重新打開一次。
數(shù)據(jù)操作服務(wù)
當(dāng)前操作數(shù)據(jù)一般都是使用ORM框架,這里以hibernate為例。
代碼較多,先建立幾個(gè)實(shí)體對(duì)象(省略getset):
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String isbn;
private String title;
private String description;
@ManyToOne
private Author author;
@ManyToOne
private Publisher publisher;
@ManyToMany
private List<Publisher.Reviewer> reviewers;
protected Book() {
}
public Book(String isbn, String title, Author author, Publisher
publisher) {
this.isbn = isbn;
this.title = title;
this.author = author;
this.publisher = publisher;
}
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author")
private List<Book> books;
protected Author() {
}
public Author(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Entity
public class Publisher {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "publisher")
private List<Book> books;
protected Publisher() {
}
public Publisher(String name) {
this.name = name;
}
@Entity
public class Reviewer {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
protected Reviewer() {
}
public Reviewer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
然后創(chuàng)建一個(gè)BookRepository接口:
@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
public Book findByIsbn(String isbn);
}
修改一下starter:
@Autowired
private BookRepository bookRepository;
@Override
public void run(String... strings) throws Exception {
logger.info("number of books:" + bookRepository.count());
}
可以看到,我們并沒有寫任何sql。只是用了很多注解,做了對(duì)象和表結(jié)構(gòu)的映射。然后繼承CrudRepository。
- @Entity:注解類,映射成表。注意類要有一個(gè)protect的默認(rèn)構(gòu)造器。
- @Repository 注解的接口,表示提供對(duì)數(shù)據(jù)庫的操作服務(wù)。同時(shí)spring會(huì)為這個(gè)接口創(chuàng)建對(duì)應(yīng)的bean,以備注入使用。
- CrudRepository提供了基本的CRUD操作。其他的操做,如findByIsbn。需要根據(jù)命名習(xí)慣,spring會(huì)自動(dòng)解析,如s findByNameIgnoringCase。
- @Id @GeneratedValue 自增主鍵
- @ManyToOne @ManyToMany @OneToMany表示兩個(gè)實(shí)體的對(duì)應(yīng)關(guān)系。如book和author是多對(duì)一的關(guān)系。
調(diào)度執(zhí)行器
實(shí)現(xiàn)每10秒執(zhí)行一次。
在BookPubApplication加上@EnableScheduling注解。
在starter加一個(gè)方法
@Scheduled(initialDelay = 1000, fixedRate = 10000)
public void run(){
logger.info("number of books:" + bookRepository.count());
}
原理可以看下,@EnableScheduling注解,它引入SchedulingConfiguration類。這個(gè)類會(huì)創(chuàng)建一個(gè)ScheduledAnnotationBeanPostProcessor。它會(huì)掃描被@Scheduled注解的無參方法。注意方法一定要是無參的。