Panduan untuk SqlResultSetMapping

1. Perkenalan

Dalam panduan ini, kita akan melihat SqlResultSetMapping , dari Java Persistence API (JPA).

Fungsionalitas inti di sini melibatkan kumpulan hasil pemetaan dari pernyataan SQL database ke dalam objek Java.

2. Penyiapan

Sebelum kita melihat penggunaannya, mari kita lakukan beberapa pengaturan.

2.1. Ketergantungan Maven

Dependensi Maven yang kami butuhkan adalah Hibernate dan H2 Database. Hibernate memberi kita implementasi spesifikasi JPA. Kami menggunakan H2 Database untuk database dalam memori.

2.2. Database

Selanjutnya, kami akan membuat dua tabel seperti yang terlihat di sini:

CREATE TABLE EMPLOYEE (id BIGINT, name VARCHAR(10));

The KARYAWAN toko tabel satu hasil Entity objek. SCHEDULE_DAYS berisi rekaman yang ditautkan ke tabel EMPLOYEE oleh kolom employeeId:

CREATE TABLE SCHEDULE_DAYS (id IDENTITY, employeeId BIGINT, dayOfWeek VARCHAR(10));

Skrip untuk pembuatan data dapat ditemukan dalam kode untuk panduan ini.

2.3. Objek Entitas

Objek Entity kita akan terlihat serupa:

@Entity public class Employee { @Id private Long id; private String name; }

Objek entitas mungkin diberi nama berbeda dari tabel database. Kita dapat membuat anotasi kelas dengan @ Table untuk memetakannya secara eksplisit:

@Entity @Table(name = "SCHEDULE_DAYS") public class ScheduledDay { @Id @GeneratedValue private Long id; private Long employeeId; private String dayOfWeek; }

3. Pemetaan Skalar

Sekarang setelah kita memiliki data, kita dapat mulai memetakan hasil kueri.

3.1. ColumnResult

Sementara SqlResultSetMapping dan Query annotation bekerja pada kelas Repository juga, kami menggunakan annotation pada kelas Entity dalam contoh ini.

Setiap anotasi SqlResultSetMapping hanya membutuhkan satu properti, nama. Namun, tanpa salah satu jenis anggota, tidak ada yang akan dipetakan. Jenis anggota adalah ColumnResult , ConstructorResult , dan EntityResult .

Dalam kasus ini, ColumnResult memetakan setiap kolom ke jenis hasil skalar:

@SqlResultSetMapping( name="FridayEmployeeResult", columns={@ColumnResult(name="employeeId")})

The ColumnResult properti nama mengidentifikasi kolom di query kita:

@NamedNativeQuery( name = "FridayEmployees", query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'", resultSetMapping = "FridayEmployeeResult") 

Perhatikan bahwa nilai resultSetMapping dalam anotasi NamedNativeQuery kita penting karena cocok dengan properti name dari deklarasi ResultSetMapping kita .

Hasilnya, kumpulan hasil NamedNativeQuery dipetakan seperti yang diharapkan. Demikian juga, StoredProcedure API membutuhkan asosiasi ini.

3.2. Tes ColumnResult

Kami memerlukan beberapa objek khusus Hibernate untuk menjalankan kode kami:

@BeforeAll public static void setup() { emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day"); em = emFactory.createEntityManager(); }

Akhirnya, kami memanggil kueri bernama untuk menjalankan pengujian kami:

@Test public void whenNamedQuery_thenColumnResult() { List employeeIds = em.createNamedQuery("FridayEmployees").getResultList(); assertEquals(2, employeeIds.size()); }

4. Pemetaan Konstruktor

Mari kita lihat kapan kita perlu memetakan hasil yang disetel ke seluruh objek.

4.1. ConstructorResult

Serupa dengan contoh ColumnResult kami , kami akan menambahkan anotasi SqlResultMapping pada kelas Entitas kami , ScheduledDay . Namun, untuk memetakan menggunakan konstruktor, kita perlu membuatnya:

public ScheduledDay ( Long id, Long employeeId, Integer hourIn, Integer hourOut, String dayofWeek) { this.id = id; this.employeeId = employeeId; this.dayOfWeek = dayofWeek; }

Selain itu, pemetaan menentukan kelas dan kolom target (keduanya wajib):

@SqlResultSetMapping( name="ScheduleResult", classes={ @ConstructorResult( targetClass=com.baeldung.sqlresultsetmapping.ScheduledDay.class, columns={ @ColumnResult(name="id", type=Long.class), @ColumnResult(name="employeeId", type=Long.class), @ColumnResult(name="dayOfWeek")})})

Urutan ColumnResults sangat penting. Jika kolom rusak, konstruktor akan gagal diidentifikasi. Dalam contoh kita, urutannya cocok dengan kolom tabel, jadi sebenarnya tidak diperlukan.

@NamedNativeQuery(name = "Schedules", query = "SELECT * FROM schedule_days WHERE employeeId = 8", resultSetMapping = "ScheduleResult")

Perbedaan unik lainnya untuk ConstructorResult adalah bahwa instansiasi objek yang dihasilkan sebagai "baru" atau "terlepas". Entitas yang dipetakan akan berada dalam status terlepas ketika kunci utama yang cocok ada di EntityManager, jika tidak maka akan baru.

Terkadang kami mungkin mengalami kesalahan runtime karena ketidakcocokan tipe data SQL ke tipe data Java. Oleh karena itu, kita dapat secara eksplisit mendeklarasikannya dengan type.

4.2. Uji ConstructorResult

Mari kita uji ConstructorResult dalam pengujian unit:

@Test public void whenNamedQuery_thenConstructorResult() { List scheduleDays = Collections.checkedList( em.createNamedQuery("Schedules", ScheduledDay.class).getResultList(), ScheduledDay.class); assertEquals(3, scheduleDays.size()); assertTrue(scheduleDays.stream().allMatch(c -> c.getEmployeeId().longValue() == 3)); }

5. Pemetaan Entitas

Terakhir, untuk pemetaan entitas sederhana dengan lebih sedikit kode, mari kita lihat EntityResult .

5.1. Entitas tunggal

EntityResult requires us to specify the entity class, Employee. We use the optional fields property for more control. Combined with FieldResult, we can map aliases and fields that do not match:

@SqlResultSetMapping( name="EmployeeResult", entities={ @EntityResult( entityClass = com.baeldung.sqlresultsetmapping.Employee.class, fields={ @FieldResult(name="id",column="employeeNumber"), @FieldResult(name="name", column="name")})})

Now our query should include the aliased column:

@NamedNativeQuery( name="Employees", query="SELECT id as employeeNumber, name FROM EMPLOYEE", resultSetMapping = "EmployeeResult")

Similarly to ConstructorResult, EntityResult requires a constructor. However, a default one works here.

5.2. Multiple Entities

Mapping multiple entities is pretty straightforward once we have mapped a single Entity:

@SqlResultSetMapping( name = "EmployeeScheduleResults", entities = { @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.Employee.class), @EntityResult(entityClass = com.baeldung.sqlresultsetmapping.ScheduledDay.class)

5.3. EntityResult Tests

Let's have a look at EntityResult in action:

@Test public void whenNamedQuery_thenSingleEntityResult() { List employees = Collections.checkedList( em.createNamedQuery("Employees").getResultList(), Employee.class); assertEquals(3, employees.size()); assertTrue(employees.stream().allMatch(c -> c.getClass() == Employee.class)); }

Since the multiple entity results join two entities, the query annotation on only one of the classes is confusing.

Oleh karena itu, kami mendefinisikan kueri dalam pengujian:

@Test public void whenNamedQuery_thenMultipleEntityResult() { Query query = em.createNativeQuery( "SELECT e.id, e.name, d.id, d.employeeId, d.dayOfWeek " + " FROM employee e, schedule_days d " + " WHERE e.id = d.employeeId", "EmployeeScheduleResults"); List results = query.getResultList(); assertEquals(4, results.size()); assertTrue(results.get(0).length == 2); Employee emp = (Employee) results.get(1)[0]; ScheduledDay day = (ScheduledDay) results.get(1)[1]; assertTrue(day.getEmployeeId() == emp.getId()); }

6. Kesimpulan

Dalam panduan ini, kami melihat opsi berbeda untuk menggunakan anotasi SqlResultSetMapping . SqlResultSetMapping adalah bagian penting dari Java Persistence API.

Potongan kode dapat ditemukan di GitHub.