เนื้อหาของวงจรของบทความ วันนี้เรากำลังสรุปการทำงานกับฐานข้อมูลซึ่งเป็นส่วนหนึ่งของโครงการของเรา หากคุณทำทุกอย่างถูกต้องแล้ว คุณควรมี pom ที่มีการขึ้นต่อกันดังต่อไปนี้:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
และนี่คือโครงสร้างของโครงการ รู้หรือไม่ ว่าเราจำใครไม่ได้มานาน? นี่คือ application.yml เราไม่ได้พูดถึงเรื่องนี้มากนักในบทความก่อนหน้านี้ ตอนนี้เรามาลบมันกันเถอะ! ใช่ ใช่ ลบ แค่นั้นแหละ! ถ้าเราเปิดตัวโปรเจ็กต์ตอนนี้ ทุกอย่างก็จะเหมือนเดิม ลองทำดู สิ่งนี้เกิดขึ้นเนื่องจาก Spring เองได้รับการกำหนดค่าด้วยการตั้งค่าเริ่มต้น ตอนนี้เราต้องคืนไฟล์ yml ของเรากลับไปที่โฟลเดอร์ทรัพยากร แต่เรายังคงต้องการมัน: application.yml (ชื่อควรจะเหมือนกัน)
spring:
datasource:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:test;
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate.ddl-auto: create
generate-ddl: true
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
ครั้งล่าสุด เราได้นำการสืบค้นหลายรายการไปใช้กับฐานข้อมูลโดยใช้วิธีการของอินเทอร์เฟซ JpaRepository<>:
//сохранить одну запись в таблицу фруктов
public void save(FruitEntity fruitEntity){
fruitRepository.save(fruitEntity);
}
//получить все записи из таблицы фруктов
public List<FruitEntity> getAll(){
return fruitRepository.findAll();
}
//сохранить несколько записей в таблицу фруктов
public void saveAll(List<FruitEntity> fruits){
fruitRepository.saveAll(fruits);
}
หากคุณอ่านเกี่ยวกับSQLอย่างที่ฉันขอให้คุณทำครั้งล่าสุด คุณควรรู้ว่าการดำเนินการดังกล่าวกับฐานข้อมูลจะต้องดำเนินการโดยใช้คำสั่ง SQL แต่ไม่มีคำใบ้เกี่ยวกับสิ่งนี้ในโครงการ แม้แต่ในบันทึกของคอนโซลก็ไม่มีอะไรที่คล้ายกัน มาหาพวกมันกัน เปิด application.yml ค้นหา show-sql: line ตรงนั้น (show sql) แล้วเปลี่ยน false เป็นจริง เราเปิดตัวโครงการและดูที่คอนโซล บันทึกจะเต็มไปด้วยรายการใหม่ที่คล้ายคลึงกับ SQL จริงๆ แล้วส่วนใหญ่เข้าใจได้ไม่ยาก เช่น:
Hibernate: drop table fruit_table if exists //удалить таблицу fruit_table если она есть
Hibernate: drop table provider_table if exists
Hibernate: create table fruit_table (id_fruit integer not null, fruit_name varchar(255), provider_code integer, primary key (id_fruit))//создать таблицу fruit_table с полями id_fruit тип integer not null, fruit_name тип varchar(255), provider_code тип integer, назначить первичным ключем поле id_fruit
Hibernate: create table provider_table (id_provider integer not null, provider_name varchar(255), primary key (id_provider))
แต่รายการนี้อาจทำให้เกิดคำถามมากมายเนื่องจากมีเครื่องหมายคำถาม:
Hibernate: insert into fruit_table (fruit_name, provider_code, id_fruit) values (?, ?, ?)
ลองคิดอย่างมีเหตุผล: ประการแรก เราเห็นคำว่า Hibernate ซึ่งหมายความว่าชายผู้มีความลับคนนี้วางอุ้งเท้าขนปุยไว้ที่นี่ หลังจากอ่านเกี่ยวกับเขาบนอินเทอร์เน็ต เราได้เรียนรู้ว่าคุณไฮเบอร์ กำลังนำแบบจำลอง ORM ไปใช้ โมเดลเชิงวัตถุสัมพันธ์อธิบายความสัมพันธ์ระหว่างวัตถุซอฟต์แวร์และบันทึกในฐานข้อมูล หลังจากแก้ไขแนวคิดนี้แล้ว เรายังคงคิดอย่างมีเหตุผลต่อไป ในด้านหนึ่ง เรามี วัตถุ FruitEntityมันมีสามฟิลด์: รหัสจำนวนเต็ม; ผลไม้สตริงชื่อ; รหัสผู้ให้บริการจำนวนเต็ม ในทางกลับกัน เรามีตารางในฐานข้อมูลFruit_tableพร้อมช่องid_fruit type integer, Fruit_name type varchar(255) , provider_code type integer โดยทั่วไปแล้ว Hibernate จะใช้ วัตถุ FruitEntityดึงค่าของฟิลด์ของวัตถุออกมาและเขียนลงในฟิลด์ตารางที่เกี่ยวข้อง ฉันมีคำถามสำหรับคุณ: ดูสิใน คลาส InitiateUtilsเราได้ดำเนินการเติมตารางผลไม้ แต่ด้วยเหตุผลบางอย่างเราตั้งค่าให้มีเพียงสองฟิลด์ ส่วนที่สามอยู่ที่ไหน
new FruitEntity()
.setFruitName("Fruit1")//раз
.setProviderCode(Math.abs(new Random().nextInt() % 10)),//два
//три???
ฉันแน่ใจว่าคุณจะคิดออกเอง นอกจากนี้ เราได้พูดคุยสั้น ๆ เกี่ยวกับปัญหานี้ในบทความก่อนหน้าสุดท้าย ขั้นแรก หาว่าช่องไหนไม่อยู่ที่นี่ แล้วคุณจะเข้าใจทุกอย่าง ทำได้ดีมาก Hiber สร้างคำขอมากมายให้เรา แต่เราไม่ได้ตาบอด ลองใช้วิธีเพิ่มเติมจาก อินเทอร์เฟซ JpaRepository<> ในคลาส FruitService กันดีกว่า
//возвращает запись из таблицы по id
public Optional<FruitEntity> getById(Integer id){
return fruitRepository.findById(id);
}
//удаляет запись из таблицы по id
public void delById(Integer id){
fruitRepository.deleteById(id);
}
//возвращает true or false при поиске в таблице Фруктов an object который соответствует типу FruitEntity or принадлежит к типу an object который наследуется от FruitEntity
public Boolean exist(Example<? extends FruitEntity> example){
return fruitRepository.exists(example);
}
ใช้วิธีการเดียวกันในคลาส ProviderService จากนั้นใช้ใน คลาส InitiateUtilsสำหรับFruitEntityและProviderEntityและพิมพ์ผลลัพธ์ไปยังคอนโซล (อย่างไรก็ตาม หากคุณไม่ทราบ คุณสามารถเขียน “System.out.println()” ได้อย่างรวดเร็วโดยพิมพ์ sout แล้วกด Enter สิ่งเดียวกันนี้ใช้ได้กับ “public static void main(String[] args){} ” เพียงพิมพ์ psvm แล้วคุณก็ไป ฯลฯ ) ฉันคิดว่าคุณได้จัดการกับมันแล้วและเราพร้อมที่จะเดินหน้าต่อไป ไปที่ อินเทอร์เฟซ FruitRepositoryและเริ่มพิมพ์ (คือพิมพ์และไม่คัดลอก) วิธีการต่อไปนี้: List<FruitEntity> f คุณควรได้รับสิ่งต่อไปนี้ เพียงเรียกวิธีการราวกับว่าคุณกำลังเขียนแบบสอบถาม: findById(Integer id ) - ค้นหาวัตถุด้วย id; countFruitEntityByFruitName(String name) - จะนับจำนวนผลไม้ที่มีชื่อเฉพาะ เหล่านี้เป็นแบบสอบถามที่สร้างโดยชื่อของวิธีการ อย่าลืมอ่านเกี่ยวกับพวกเขาและใช้วิธีการระหว่าง (จำนวนเต็มจาก, จำนวนเต็มถึง) ในคลาส FruitServiceเพื่อค้นหารายการ <FruitEntity> ด้วยค่าของฟิลด์ provider_codeที่รวมอยู่ใน เป็นระยะเวลาหนึ่งและแสดงผลการทำงานในคอนโซล ตัวอย่างเช่น ค้นหาผลไม้ทั้งหมดที่มีหมายเลขซัพพลายเออร์อยู่ระหว่าง 5 ถึง 7 อย่ารีบอ่านเพิ่มเติมจนกว่าคุณจะนำวิธีนี้ไปใช้ เนื่องจากจะใช้เวลาไม่นาน ดังที่คุณอาจได้อ่านในบทความเกี่ยวกับการสืบค้นตามชื่อเมธอด: “คุณไม่สามารถเขียนการสืบค้นทั้งหมดเช่นนี้ได้ แต่สามารถเขียนการสืบค้นแบบธรรมดาได้” สำหรับการสืบค้นที่ซับซ้อนมากขึ้น จะใช้คำอธิบายประกอบ @Query และใช้ JPQLแทน SQL (โปรดจดบันทึกบทความนี้ด้วย) สำหรับโปรเจ็กต์ของเรา คุณสามารถสร้างการสืบค้น JOIN ได้ดังนี้:
package ru.java.rush.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.java.rush.entities.FruitEntity;
import java.util.List;
@Repository
public interface FruitRepository extends JpaRepository<FruitEntity,Integer> {
@Query("select f.fruitName, p.providerName from FruitEntity f left join ProviderEntity p on f.providerCode = p.id")
List<String> joinSting();
}
แบบสอบถาม SQL มาตรฐานจะเป็น: "select Fruit_table.fruit_name, provider_table.provider_name จาก Fruit_table left join provider_table on fruit_table.provider_code = provider_table.id" ที่นี่คุณสามารถสร้างการติดต่อได้อย่างง่ายดาย: f ruit_tableคือFruitEntiy fโดยที่FruitEntiyเป็นประเภทของตัวแปรfคือชื่อของมัน นั่นคือ SQL ทำงานกับตารางและฟิลด์ และ JPQL กับอ็อบเจ็กต์และฟิลด์ของพวกมัน อีกครั้งFruit_table.fruit_nameคือf.fruitName ; เนื่องจากเราทำงานกับวัตถุ เราจึงสามารถส่งออกวัตถุได้: มาเขียนวิธี FruitRepository อีกวิธีหนึ่งกัน
@Query("select f from FruitEntity f join ProviderEntity p on f.providerCode = p.id")
List<FruitEntity> joinFruit();
ลองใช้ทั้งสองวิธีในคลาส FruitService
public List<String> joinString(){
return fruitRepository.joinSting();
}
public List<FruitEntity> joinFruit(){
return fruitRepository.joinFruit();
}
ฟังดูไม่ได้แย่ แต่พวกเขายังคงใช้ SQL เก่าที่ดีสำหรับการสืบค้นที่ซับซ้อน
@Query(
value = "select fruit_table.fruit_name, provider_table.provider_name from fruit_table join provider_table on fruit_table.provider_code = provider_table.id_provider", //по идее эту портянку надо засунуть в Howой нибудь Enum
nativeQuery = true) //нужно только пометить только nativeQuery = true
ListList<String> joinSqlFruit();
และเราใช้มันทั้งหมดใน คลาส InitiateUtils
System.out.println("\nТаблица фруктов и их поставщиков");
for (String join : fruitService.joinString()) {
System.out.println(join);
}
System.out.println("\nТаблица фруктов и их поставщиков");
for (FruitEntity join : fruitService.joinFruit()) {
System.out.println(join);
}
System.out.println("\nТаблица фруктов и их поставщиков");
for (String join : fruitService.joinSqlFruit()) {
System.out.println(join);
}
เราเปิดตัวโครงการและดูบันทึกใหม่ในคอนโซล: ตารางผลไม้และซัพพลายเออร์ของพวกเขา Fruit1, null Fruit2, ผู้ให้บริการ 5 ผลไม้ 3, ผู้ให้บริการ 2 ผลไม้ 4, ผู้ให้บริการ 5 ผลไม้ 5, null Fruit6, null Fruit7, null Fruit8, null Fruit9, null ตารางผลไม้และของพวกเขา ซัพพลายเออร์ FruitEntity(id= 2, FruitName=Fruit2, providerCode=5) FruitEntity(id=3, FruitName=Fruit3, providerCode=2) FruitEntity(id=4, FruitName=Fruit4, providerCode=5) ตารางผลไม้และซัพพลายเออร์ Fruit2 ,Provider5 Fruit3,Provider2 Fruit4 ,Provider5 ใช่ หากคุณเบื่อกับ "หลอก" และเพียงแค่คำสั่ง SQL ในคอนโซลแล้ว คุณสามารถคืนค่า false แทนที่ในไฟล์ yaml ได้ เหตุใดตารางแรกจึงมีค่าว่าง คุณจะพบว่าคุณอ่านเกี่ยวกับJOIN SQL หรือ ไม่ ดังนั้นเราจึงดำเนินการค้นหาฐานข้อมูลเสร็จแล้ว ฉันแน่ใจว่าคุณยังมีคำถามมากมาย แต่ฉันหวังว่าคุณจะค้นหาคำตอบสำหรับพวกเขา ฉันพยายามเน้นเส้นทางการค้นหา เรามาลองสรุปทุกสิ่งที่เราได้เรียนรู้ในช่วงเวลานี้: 1. คุณสามารถรันเว็บเซิร์ฟเวอร์ใน Spring ได้และก็ไม่ยาก 2. เพื่อทำความเข้าใจว่ามันทำงานอย่างไร คุณต้องศึกษาทฤษฎีก่อน เกี่ยวกับหนังสือ บทความเกี่ยวกับสปริง บทความเกี่ยวกับสิ่งที่มีประโยชน์ 3. เพื่อให้เข้าใจในการเปลี่ยนเป็นโค้ดจริง คุณต้องเขียนโค้ด และก่อนที่จะดำเนินการต่อ ให้ลงมือทำโปรเจ็กต์ง่ายๆ บน spring-boot และเป็นการดีกว่าที่จะไม่คัดลอกโค้ดที่เขียน แต่ควรเขียนใหม่ ฉันจะโพสต์โครงการที่คุณและฉันกำลังทำอยู่ที่นี่ แต่ฉันจะขึ้นอยู่กับการรับรู้ของคุณ และฉันมั่นใจว่าคุณจะไม่คัดลอกและวางโดยไร้เหตุผล ลิงก์ไปยัง ที่เก็บ git clone https://FromJava@bitbucket.org/FromJava/jd.git สำหรับผู้ที่ไม่ทราบวิธีใช้ลิงก์นี้ ฉันขอแนะนำให้ใช้สองโครงการฝึกอบรม: โครงการเกี่ยวกับการพ่นสีรถยนต์: ชั้นหนึ่ง: CarEntity{ จำนวนเต็ม รหัส; สตริงชื่อโมเดล; สีสาย; } คลาสที่สอง: ColorEntity{ รหัสจำนวนเต็ม; สีสาย; ราคาจำนวนเต็ม; } กรอกฐานข้อมูล (คิดชื่อที่สมจริงเพื่อให้เข้าใจได้ง่ายขึ้น) ใช้งาน: , เอนทิตี, ที่เก็บข้อมูล, บริการ, สร้างแบบสอบถามมาตรฐานและแบบข้ามตาราง (การทาสี BMW สีแดงราคาเท่าไหร่ สีอะไร แพงที่สุด เขียนโมเดลบนคอนโซลตามลำดับตัวอักษรและอื่น ๆ ); โครงการห้องสมุด: ชั้นหนึ่ง: BookEntity{ Integer id; ชื่อสตริงBook; จำนวนเต็มปีสร้าง; จำนวนเต็ม autorId; } คลาสที่สอง: AutorEntity{ รหัสจำนวนเต็ม; สตริง firstNameAutor; สตริงนามสกุล Autor; } กรอกฐานข้อมูล (คิดชื่อตามความเป็นจริงเพื่อให้เข้าใจได้ง่ายขึ้น) นำไปใช้: เอนทิตี พื้นที่เก็บข้อมูล บริการ สร้างแบบสอบถามมาตรฐานและระหว่างตาราง (ใครเขียนหนังสือเล่มไหน หนังสือเล่มไหนเขียนก่อน หนังสือเล่มไหนเขียน ตั้งแต่ปี 1800 ถึง 1900 ผู้เขียนคนไหนเขียนหนังสือมากที่สุด); ตัวอย่างการกรอก ฐานข้อมูลโครงการ "ห้องสมุด" ตารางหนังสือ BookEntity(id=1, nameBook=Woe from Wit, yearCreat=1824, authorId=1) BookEntity(id=2, nameBook=War and Peace, yearCreat=1863, authorId=2) BookEntity(id=3, nameBook= Mtsyri, yearCreat=1838, authorId=3) BookEntity(id=4, nameBook=Eugene Onegin, yearCreat=1833, authorId=4) ตารางผู้เขียน AuthorEntity(id=1, firstNameAuthor=Alexander, LastNameAuthor=Griboyedov) AuthorEntity(id=2 , firstNameAuthor=Lev, LastNameAuthor=Tolstoy) AuthorEntity(id=3, firstNameAuthor=Mikhail, LastNameAuthor=Lermontov) AuthorEntity(id=4, firstNameAuthor=Alexander, LastNameAuthor=Pushkin) ขอให้ ทุกคนโชคดีแล้วพบกันใหม่!
GO TO FULL VERSION