Tietokantojen käsittely ohjelmallisesti
Tietokantataulut ja luokat ovat hyvin samankaltaisia. Tietokantatauluissa määritellään sarakkeet ja viiteavaimet, luokissa määritellään attribuutit ja viitteet. Ei liene yllättävää, että tietokantatauluja kuvataan usein luokkien avulla.
ORM-työvälineet tarjoavat ohjelmistokehittäjälle mm. toiminnallisuuden tietokantataulujen luomiseen luokista, jonka lisäksi ne helpottavat kyselyjen muodostamista ja hallinnoivat luokkien välisiä viittauksia. Ohjelmoijan vastuulle jää parhaassa tapauksessa sovellukselle tarpeellisten kyselyiden toteuttaminen vain niiltä osin kuin ORM-kehykset eivät valmiina tarjoa.
Koska huomattava osa tietokantatoiminnallisuudesta on hyvin samankaltaista ("tallenna", "lataa", "poista", ...), voidaan perustoiminnallisuus piilottaa käytännössä kokonaan ohjelmoijalta. Tällöin ohjelmoijalle jää tehtäväksi usein tietokantatauluja kuvaavien luokkien sekä tietokantakyselyistä vastaavien rajapintojen määrittely. Tutustutaan tähän seuraavaksi.
Tietokantatoiminnallisuuden saa sovelluksen käyttöön lisäämällä sovellukseen seuraavat riippuvuudet. Kuten aiemmin, riippuvuudet on valmiiksi määritelty tehtäväpohjiin.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Tietokantaa käyttävät tehtäväpohjamme ovat lisäksi määritelty siten, että sovellus luo oletuksena tehtäväpohjan juuripolkuun tietokantatiedostot database.mv.db
ja database.trace.db
. Nämä määrittelyt löytyvät tehtäväpohjan kansiossa src/main/resources
olevasta tiedostosta application.properties
.
Jos haluat tyhjentää tietokannan, poista nämä tiedostot ja käynnistä sovellus uudestaan. Voit vaihtoehtoisesti aina toteuttaa ohjelmaan toiminnallisuuden tietokannan tyhjentämiseksi.
Luokan määrittely tallennettavaksi
JPA-standardin mukaan jokaisella tietokantaan tallennettavalla luokalla tulee olla annotaatio @Entity
sekä @Id
-annotaatiolla merkattu attribuutti, joka toimii tietokantataulun pääavaimena. JPA:ta käytettäessä pääavain on tyypillisesti numeerinen (Long
tai Integer
). Näiden lisäksi luokan tulee toteuttaa Serializable
-rajapinta — tämä ei vaadi muuta kuin luokkamäärittelyyn lisätyn implements Serializable
osan. Tällaisia tietokantataulun määritteleviä luokkia kutsutaan entiteeteiksi.
Numeeriselle pääavaimelle voidaan lisäksi määritellä annotaatio @GeneratedValue(strategy = GenerationType.AUTO)
, joka antaa vastuun pääavaimen arvojen luomisesta tietokannalle. Tietokantatauluun tallennettava luokka näyttää seuraavalta:
// pakkaus
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Henkilo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nimi;
// getterit ja setterit
Tietokantaan luotavien sarakkeiden ja tietokantataulujen nimiä voi muokata annotaatioiden @Column
ja @Table
avulla.
// pakkaus
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "Henkilo")
public class Henkilo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "nimi")
private String nimi;
// getterit ja setterit
Yllä oleva luokka määrittelee tietokantataulun nimeltä "Henkilo", jolla on sarakkeet "id" ja "nimi". Oletuksena taulujen ja sarakkeiden nimet luodaan muuttujien nimien perusteella, joten yllä oleva määrittely ei oikeastaan muuta mitään.
Sovelluskehys päättelee sarakkeiden tyypit automaattisesti muuttujien tyyppien perusteella. Näihin voi kuitenkin vaikuttaa — esimerkiksi tietokantaan tallennettavan merkkijonon pituuteen voi vaikuttaa @Column
-annotaation attribuutilla length
.
// pakkaus ja importit
@Entity
@Table(name = "Henkilo")
public class Henkilo extends AbstractPersistable<Long> {
@Column(name = "nimi")
private String nimi;
// getterit ja setterit
Jos tietokantataulun ja sarakkeiden annotaatioita ei eksplisiittisesti määritellä, niiden nimet päätellään luokan ja muuttujien nimistä.
// pakkaus ja importit
@Entity
public class Henkilo extends AbstractPersistable<Long> {
private String nimi;
// getterit ja setterit
Koska käytämme myös Lombok-projektia, luokkamme ei tarvitse oikeastaan edes gettereitä tai settereitä.
// pakkaus ja importit
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Henkilo extends AbstractPersistable<Long> {
private String nimi;
}
Yllä oleva luokka määrittelee tietokantataulun, jolla on pääavaimena toimiva id
-niminen sarake. Yllä pääavaimen tyypiksi on annettu Long
(... extends AbstractPersistable<Long>
). Pääavaimen arvot luodaan automaattisesti. Tämän lisäksi tietokantataululla on merkkijonomuotoinen sarake nimi
. Luokalla on myös konstruktorit, getterit, setterit sekä hashCode, equals, ja toString-metodit.
Rajapinta tietokannan käsittelyyn
Kun käytössämme on tietokantataulua kuvaava luokka, voimme luoda tietokannan käsittelyyn käytettävän rajapinnan. Spring-sovelluskehystä ja JPA-standardia käyttäessämme tietokannan käsittelyyn tarkoitettu rajapintamme perii valmiin JpaRepository
-rajapinnan, joka määrittelee normaalin CRUD-toiminnallisuuden (create, read, update, delete) sekä joukon muita metodeja.
Perittävälle JpaRepository
-rajapinnalle annetaan kaksi tyyppiparametria. Ensimmäisellä tyyppiparametrilla kerrotaan tietokantataulua kuvaava luokka ja toisella tyyppiparametrilla tietokantataulun pääavaimen tyyppi.
Kutsutaan tätä rajapintaoliota nimellä HenkiloRepository
. Esimerkissä oletetaan, että luokka Henkilo
sijaitsee pakkauksessa domain
.
// pakkaus
import domain.Henkilo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface HenkiloRepository extends JpaRepository<Henkilo, Long> {
}
Emme tee rajapinnasta konkreettista toteutusta. Spring luo automaattisesti rajapinnan toteuttavan olion sovelluksemme käynnistyksen yhteydessä.
Tietokantaa käsittelevän olion tuominen kontrolleriin
Kun olemme luoneet rajapinnan HenkiloRepository
, voimme lisätä sen kontrolleriluokkaan. Tämä tapahtuu määrittelemällä tietokanta-abstraktiota kuvaavan rajapinnan olio kontrollerin oliomuuttujaksi. Oliomuuttujalle asetetaan lisäksi annotaatio @Autowired
. Tämä @Autowired
liittyy ensimmäisessä osassa käsiteltyihin termeihin Inversion of Control ja Dependency Injection. Spring luo käynnistyksen yhteydessä HenkiloRepository
rajapinnan toteuttavan olion, jonka se sitten injektoi @Autowired
-annotaatiolla merkittyihin HenkiloRepository
-muuttujiin.
// ...
@Controller
public class HenkiloController {
@Autowired
private HenkiloRepository henkiloRepository;
// ...
}
HenkiloRepository
-olion kautta. Katso JpaRepository-luokan API, joka sisältää rajapinnan tarjoamien metodien kuvauksia. Huomaa, että JpaRepository perii mm. rajapinnan CrudRepository, jonka metodit ovat myös ohjelmiemme käytössä.Voimme esimerkiksi toteuttaa tietokannassa olevien olioiden listauksen sekä yksittäisen olion lisäämisen seuraavalla tavalla.
// ...
@Controller
public class HenkiloController {
@Autowired
private HenkiloRepository henkiloRepository;
@GetMapping("/")
public String list(Model model) {
model.addAttribute("list", henkiloRepository.findAll());
return "henkilot"; // tässä oletetaan erillinen tiedosto henkilot.html
}
@PostMapping("/")
public String create(@RequestParam String nimi) {
henkiloRepository.save(new Henkilo(nimi));
return "redirect:/";
}
}
:
Log in to view the quiz