U prethodnom tekstu smo upoznali osnove Spring Frameworka na primjeru male aplikacije koja omogućava čuvanje detalja o filmovima i glumcima. Aplikacija se sastoji od tri sloja: domenski sloj, sloj repozitorija i servisni sloj. Prije nego što počnemo objašnjavati koncepte Spring Boot-a, nadogradićemo našu aplikaciju uvodeći u igru bazu podataka, odnosno čuvajući detalje o filmovima i glumcima u H2 bazi podataka, a koja će se podizati i gasiti zajedno sa cijelom aplikacijom.
Prva stvar koju ćemo uraditi jeste konfiguracija novih dependency-ja u pom.xml datoteci. U ovom slučaju su nam potrebne dvije nove biblioteke: H2 jdbc driver i spring-jdbc biblioteka:
Obzirom da želimo da naša aplikacija kreira šemu baze podataka i popuni je sa odgovarajućim podacima, unutar src/main/resources foldera ćemo kreirati dva .sql fajla.
#schema.sql
DROP TABLE IF EXISTS ACTORS;
DROP TABLE IF EXISTS MOVIES;
DROP TABLE IF EXISTS ACTORS_MOVIES;
CREATE TABLE IF NOT EXISTS ACTORS(
ID INT PRIMARY KEY,
FIRST_NAME VARCHAR(40) NOT NULL,
LAST_NAME VARCHAR(40) NOT NULL
);
CREATE TABLE IF NOT EXISTS MOVIES(
ID INT PRIMARY KEY,
TITLE VARCHAR(200) NOT NULL,
DURATION INT NOT NULL,
);
CREATE TABLE IF NOT EXISTS ACTORS_MOVIES(
ID INT AUTO_INCREMENT PRIMARY KEY,
ACTOR_ID INT NOT NULL,
MOVIE_ID INT NOT NULL,
FOREIGN KEY (ACTOR_ID) REFERENCES ACTORS(ID),
FOREIGN KEY (MOVIE_ID) REFERENCES MOVIES(ID)
);
#data.sql
INSERT INTO ACTORS (ID, FIRST_NAME, LAST_NAME) VALUES (1, 'Brad', 'Pitt');
INSERT INTO ACTORS (ID, FIRST_NAME, LAST_NAME) VALUES (2, 'Edward', 'Norton');
INSERT INTO MOVIES (ID, TITLE, DURATION) VALUES (1, 'Fight Club', 139);
INSERT INTO MOVIES (ID, TITLE, DURATION) VALUES (2, 'Zodiac', 157);
INSERT INTO ACTORS_MOVIES(ACTOR_ID, MOVIE_ID) VALUES (1, 1);
INSERT INTO ACTORS_MOVIES(ACTOR_ID, MOVIE_ID) VALUES (2, 1);
INSERT INTO ACTORS_MOVIES(ACTOR_ID, MOVIE_ID) VALUES (1, 2);
Da bismo konfigurisali bazu podataka i omogućili rad sa njenim podacima, koristeći navedene biblioteke i .sql fajlove, potrebno je kreirati odgovarajući broj java (bean) objekata. Dva najvažnija objekta su implementacija interfejsa javax.sql.DataSource i objekat tipa org.springframework,jdbc.core.JdbcTemplate:
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource("jdbc:h2:~/cinema", "sa", "");
dataSource.setDriverClassName("org.h2.Driver");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
DataSource je objekat koji je zadužen za uspostavljanje konekcije sa bazom podataka. U ovom slučaju, baza podataka se nalazi u datoteci cinema koja se nalazi unutar HOME foldera. Ukoliko datoteka ne postoji, ista će biti kreirana pri prvom pokretanju aplikacije. JdbcTemplate sa druge strane predstavlja Spring-ovu apstrakciju JDBC API-ja, odnosno helper objekat koji nam omogućava da lakše i efikasnije komuniciramo sa bazom podataka. Tako na primjer, ukoliko želimo napisati metod koji čita podatke iz tabele baze podataka i iste konvertuje u odgovarajući java objekat, možemo napisati:
@Repository
public class DatabaseActorRepository implements ActorRepository{
@Override
public Actor findById(int id) {
Movie movie = jdbcTemplate.queryForObject("SELECT * FROM ACTORS WHERE ID = ?", new Object[] {id}, new BeanPropertyRowMapper>(Actor.class));
return movie;
}
}
JdbcTemplate metoda queryForObject omogućava da rezultat SQL upita konvertujemo u objekat klase Actor, koristeći BeanPropertyRowMapper instancu prilikom navedene konverzije. BeanPropertyRowMapper konvertuje dohvaćeni slog baze podataka u java objekat poređenjem naziva kolona tabele i atributa klase. Tako, kolone ID, FIRST_NAME i LAST_NAME će biti konvertovane u id, firstName i lastName atribute klase Actor.
Konačno, potrebna nam je i logika koja će kreirati objekte baze podataka i popuniti ih sa podacima iz .sql fajlova. U ovu svrhu ćemo kreira dva bean objekta:
@Bean
public DatabasePopulator databasePopulator() {
return new ResourceDatabasePopulator(new ClassPathResource("schema.sql"), new ClassPathResource("data.sql"));
}
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource, DatabasePopulator databasePopulator) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator);
initializer.setEnabled(true);
return initializer;
}
DataSourceInitializer inicira proces inicijalizacije baze podataka, a DatabasePopulator izvršava sql skripte schema.sql i data.sql.
Svi navedeni bean objekti (dataSource, jdbcTemplate, databasePopulator i dataSourceInitializer) se nalaze u klasi Config koja je označena anotacijom @Configuration. U skladu sa ovom anotacijom, Spring kontejner će kreirati navedene objekte izvršavanjem @Bean metoda što je to moguće ranije u životnom ciklusu aplikacije. Ovo garantuje rano uspostavljanje konekcije na bazu podataka i izvršavanje sql skripti, a jednostavnom upotrebom @Autowired anotacije, bilo koja komponenta aplikacije može dohvatiti jdbcTemplate objekat i raditi sa bazom podataka na gore opisani način.
Spring Boot implementacija
Spring Boot je projekat koji se naslanja na Spring Framework i koji omogućava mnogo efikasniji i brži pristup izgradnje Spring aplikacija. Boot jednostavno skenira classpath aplikacije i ispituje importovane (Spring i third-party) biblioteke, te na osnovu tih saznanja pokušava zaključiti o kojem tipu aplikacije se radi i kreirati odgovarajuće bean objekte koje developer tipično mora kreirati u skladu sa aplikacionim zahtjevima. Tako na primjer, ukoliko Boot detektuje na classpathu H2 driver i spring-jdbc biblioteke, automatski će kreirati JdbcTemplate i DataSource objekte. Ukoliko Boot detektuje Hibernate biblioteke, automatski će kreirati veći broj objekata, npr. EntityManagetFactoryBean, ili TransactionManager objekat instanciranjem HibernateTransactionManager klase. U slučaju da koristimo Hazelcast, automatski se kreira HazelcastInstance objekat itd. Navedeni pristup se naziva autokonfiguracija.
Da bismo prethodno opisani primjer aplikacije napisali koristeći Spring Boot, potrebno je importovati odgovarajuće Boot biblioteke:
Spring Boot organizuje biblioteke u tzv. starterima, a svaki starter sadrži skup biblioteka koji je neophodan za specifične aplikacione potrebe. Tako, spring-boot-starter-jdbc sadrži sve biblioteke potrebne za rad sa bazom podataka a spring-boot-starter-web obuhvata veliki broj biblioteka za rad sa web aplikacijama. Na ovaj način developer nije oslobođen samo potrebe za importovanjem svih potrebnih biblioteka, već je oslobođen i brige za kompatibilnošću verzija tih biblioteka.
Već smo rekli da će Spring Boot na osnovu ovih informacija autokonfigurisati određene objekte, među njima DataSource i JdbcTemplate. Međutim, sjetimo se načina na koji smo to mi ručno uradili:
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource("jdbc:h2:~/cinema", "sa", "");
dataSource.setDriverClassName("org.h2.Driver");
return dataSource;
}
Postavlja se pitanje: kako Spring Boot zna da gdje se nalazi fajl baze podataka, odnosno koji jdbc url da koristi? Odgovor je da ne zna! Zapravo Boot prilikom autokonfiguracije koristi neke razumne default vrijednosti. U ovom slučaju, za jdbc url se koristi default vrijednost jdbc:h2:mem:testdb, što znači da će baza podataka biti podignuta u memoriji prilikom startupa aplikacije. Kako možemo promijeniti default vrijednost? Spring Boot prilikom autokonfiguracije čita konfiguracione fajlove u kojima možemo postaviti odgovarajuće propertije. Tako, možemo kreirati application.properties fajl unutar /src/main/resources foldera i u njega unijeti slijedeću konfiguraciju:
spring.datasource.url=jdbc:h2:~/cinema
Sada će automatski konfigurisani bean dataSource biti konektovan na cinema bazu podataka. Također, u Spring Boot implementaciji ne moramo kreirati DatabasePopulator i DataSourceInitializer objekte jer će Boot automatski pročitati schema.sql i data.sql, sve dok se ovako zovu i nalaze na classpathu! Bitno je napomenuti da je Boot dovoljno pametan da prepozna da li da koristi autokonfiguraciju ili ne, i to na osnovu pretrage Spring kontejnera. Drugim riječima, ukolimo mi sami kreiramo DataSource bean, Spring Boot neće kreirati ovaj bean za nas. Ovaj pristup omogućava neophodne kastomizacije u slučaju da default postavke ne odgovaraju našim potrebama.
Uključimo sada u naš projekat i web biblioteke:
Ova definicija iz pom.xml u naš projekat importuje veći broj biblioteka neophodnih za razvoj web aplikacija, uključujući i embedded Tomcat web server koji će biti podignut prilikom startanja aplikacije na default portu 8080. Sada možemo kreirati jednostavnog REST kontrolera:
@RestController
@RequestMapping("/")
public class HomeController {
@Autowired
CinemaService cinemaService;
@GetMapping("/movies")
public Set movies() {
return cinemaService.findAllMovies();
}
}
Nakon što pokrenemo aplikaciju, na URL-u http://localhost:8080/movies dobijamo filmove iz baze podataka, u JSON formatu:
curl -X GET http://localhost:8080/movies
[{"id":1,"title":"Fight Club","duration":139,"actors":
[{"id":2,"firstName":"Edward","lastName":"Norton"},
{"id":1,"firstName":"Brad","lastName":"Pitt"}]},
{"id":2,"title":"Zodiac","duration":157,"actors":
[{"id":1,"firstName":"Brad","lastName":"Pitt"}]}]
Boot je u ovom slučaju kreirao određeni broj objekata, uključujući DispatcherServlet, za upravljenje HTTP zahtjevima koji se proslijeđuju odgovarajućim handlerima i kontrolerima, HttpMessageConverters za transformaciju podataka u JSON, kao i Tomtcat specifični TomcatEmbeddedServletContainerFactory.
Da bi sve ovo radilo, klasa App je označena @SpringBootApplication anotacijom. Ova anotacija sadrži u sebi, između ostalih, i @EnableAutoConfiguration anotaciju a upravo ona je zadužena za automatsku konfiguraciju bean objekata o kojoj smo govorili do sada. Također, treba obratiti pažnju na SpringApplication klasu koja omogućava pokretanje aplikacije, kreirajući u pozadini odgovarajući ApplicationContext, što objašnjava činjenicu da mi to nismo morali raditi, kao u prethodnom članku.
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Ukoliko u pom.xml dodamo spring-boot-maven-plugin, .jar fajl generisan sa komandom mvn package će sadržavati i sve (spring i third-party) biblioteke, uključujući i internog Tomcat servera. To znači da je jedan fat jar fajl dovoljan za distribuciju cijele aplikacije!
Kao i obično, izvorni kod primjera korištenih u ovom članku možete preuzeti na slijedećem linku. Ovim se ujedno i završava naša mini škola Spring Frameworka. U narednim člancima ćemo pokušati predstaviti različite biblioteke, tehnologije i pristupe prilikom izrade Java enterprise-ready aplikacija. Do narednog čitanja!
Almir Pehratović, Infobip BH d.o.o.
Komentari 0 KOMENTARA
Komentiraj