Apa yang Baru di JPA 2.2

Selamat berlibur untuk semua!

Sangat tiba-tiba terjadi bahwa dimulainya grup kedua "Java Enterprise Developer" bertepatan dengan hari ke-256 tahun itu. Kebetulan? Saya kira tidak.

Yah, kami berbagi minat kedua dari belakang: hal-hal baru apa yang dibawa JPA 2.2 hasil streaming, peningkatan konversi tanggal, anotasi baru - hanya beberapa contoh perbaikan yang bermanfaat.

Ayo pergi!

Java Persistence API (JPA) adalah spesifikasi Java EE mendasar yang banyak digunakan dalam industri. Terlepas dari apakah Anda mengembangkan untuk platform Java EE atau untuk kerangka Java alternatif, JPA adalah pilihan Anda untuk menyimpan data. JPA 2.1 meningkatkan spesifikasi, yang memungkinkan pengembang untuk memecahkan masalah seperti pembuatan skema database otomatis dan pekerjaan efisien dengan prosedur yang tersimpan dalam database. Versi terbaru, JPA 2.2, meningkatkan spesifikasi berdasarkan perubahan ini.
Pada artikel ini saya akan berbicara tentang fungsionalitas baru dan memberikan contoh yang akan membantu Anda memulai. Sebagai sampel, saya menggunakan proyek "Java EE 8 Playground", yang tersedia di GitHub . Aplikasi sampel didasarkan pada spesifikasi Java EE 8 dan menggunakan JavaServer Faces (JSF), Enterprise JavaBeans (EJB), dan kerangka kerja JPA untuk kegigihan. Anda harus terbiasa dengan JPA untuk memahami apa ini.



Menggunakan JPA 2.2

JPA versi 2.2 adalah bagian dari platform Java EE 8. Perlu dicatat bahwa hanya server aplikasi yang kompatibel dengan Java EE 8 yang menyediakan spesifikasi yang siap digunakan di luar kotak. Pada saat penulisan ini (akhir 2017), ada beberapa server aplikasi semacam itu. Namun, menggunakan JPA 2.2 dengan Java EE7 mudah. Pertama, Anda perlu mengunduh file JAR yang sesuai menggunakan Maven Central dan menambahkannya ke proyek. Jika Anda menggunakan Maven dalam proyek Anda, tambahkan koordinat ke file POM Maven:

<dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> 

Kemudian, pilih implementasi JPA yang ingin Anda gunakan. Dimulai dengan JPA 2.2, EclipseLink dan Hibernate memiliki implementasi yang kompatibel. Sebagai contoh dalam artikel ini, saya menggunakan EclipseLink dengan menambahkan ketergantungan berikut:

 <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.7.0 </version> </dependency> 

Jika Anda menggunakan server yang kompatibel dengan Java EE 8, seperti GlassFish 5 atau Payara 5, Anda harus dapat menentukan area "yang disediakan" untuk dependensi ini dalam file POM. Jika tidak, tentukan area "kompilasi" untuk memasukkannya dalam perakitan proyek.

Dukungan Tanggal dan Waktu Java 8

Mungkin salah satu tambahan paling positif adalah dukungan Java 8 Date and Time API. Sejak rilis Java SE 8 pada tahun 2014, pengembang telah menggunakan solusi untuk menggunakan API Tanggal dan Waktu dengan JPA. Meskipun sebagian besar penyelesaiannya cukup mudah, kebutuhan untuk menambahkan dukungan dasar untuk API Tanggal dan Waktu yang diperbarui sudah lama terlambat. Dukungan JPA untuk API Tanggal dan Waktu mencakup jenis-jenis berikut:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.OffsetTime
  • java.time.OffsetDateTime

Untuk pemahaman yang lebih baik, saya pertama-tama akan menjelaskan bagaimana dukungan API Tanggal dan Waktu berfungsi tanpa JPA 2.2. JPA 2.1 hanya dapat bekerja dengan konstruksi tanggal yang lebih lama seperti java.util.Date dan java.sql.Timestamp . Oleh karena itu, Anda harus menggunakan konverter untuk mengubah tanggal yang disimpan dalam database menjadi desain lama yang didukung oleh JPA 2.1, dan kemudian mengubahnya menjadi API Tanggal dan Waktu yang diperbarui untuk digunakan dalam aplikasi. Konverter tanggal di JPA 2.1 yang mampu melakukan konversi tersebut mungkin terlihat seperti Listing 1. Konverter di dalamnya digunakan untuk mengkonversi antara LocalDate dan java.util.Date .

Listing 1

 @Converter(autoApply = true) public class LocalDateTimeConverter implements AttributeConverter<LocalDate, Date> { @Override public Date convertToDatabaseColumn(LocalDate entityValue) { LocalTime time = LocalTime.now(); Instant instant = time.atDate(entityValue) .atZone(ZoneId.systemDefault()) .toInstant(); return Date.from(instant); } @Override public LocalDate convertToEntityAttribute(Date databaseValue){ Instant instant = Instant.ofEpochMilli(databaseValue.getTime()); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate(); } } 

JPA 2.2 tidak perlu lagi menulis konverter seperti itu, karena Anda menggunakan tipe waktu tanggal yang didukung. Dukungan untuk tipe-tipe tersebut sudah ada di dalam, jadi Anda bisa menentukan tipe yang didukung di bidang kelas entitas tanpa kode tambahan. Cuplikan kode di bawah ini menunjukkan konsep ini. Perhatikan bahwa tidak perlu menambahkan anotasi ke kode @Temporal , karena pemetaan tipe terjadi secara otomatis.

 public class Job implements Serializable { . . . @Column(name = "WORK_DATE") private LocalDate workDate; . . . } 

Karena tipe tanggal-waktu yang didukung adalah objek kelas satu di JPA, mereka dapat ditentukan tanpa upacara tambahan. Dalam JPA 2.1 @Temporal anotasi harus dijelaskan di semua bidang dan properti konstan tipe java.util.Date dan java.util.Calendar .

Perlu dicatat bahwa hanya bagian dari tipe data-waktu yang didukung dalam versi ini, tetapi konverter atribut dapat dengan mudah dihasilkan untuk bekerja dengan tipe lain, misalnya, untuk mengonversi LocalDateTime ke ZonedDateTime . Masalah terbesar dalam menulis konverter seperti itu adalah untuk menentukan cara terbaik untuk mengkonversi antara berbagai jenis. Untuk mempermudah, konverter atribut sekarang dapat diterapkan. Saya akan memberikan contoh implementasi di bawah ini.

Kode dalam Listing 2 menunjukkan bagaimana mengkonversi waktu dari LocalDateTime ke ZonedDateTime .

Listing 2

 @Converter public class LocalToZonedConverter implements AttributeConverter<ZonedDateTime, LocalDateTime> { @Override public LocalDateTime convertToDatabaseColumn(ZonedDateTime entityValue) { return entityValue.toLocalDateTime(); } @Override public ZonedDateTime convertToEntityAttribute(LocalDateTime databaseValue) { return ZonedDateTime.of(databaseValue, ZoneId.systemDefault()); } } 

Secara khusus, contoh ini sangat mudah karena ZonedDateTime berisi metode yang mudah dikonversi. Konversi terjadi dengan memanggil metode toLocalDateTime() . Konversi terbalik dapat dilakukan dengan memanggil metode ZonedDateTimeOf() dan meneruskan nilai LocalDateTime bersama dengan ZoneId untuk menggunakan zona waktu.

Konverter Atribut Tertanam

Konverter atribut adalah tambahan yang sangat bagus untuk JPA 2.1, karena mereka memungkinkan tipe atribut menjadi lebih fleksibel. Pembaruan JPA 2.2 menambahkan kemampuan yang berguna untuk membuat konverter atribut dapat diterapkan. Ini berarti bahwa Anda dapat menanamkan sumber daya Konteks dan Ketergantungan Injeksi (CDI) langsung ke konverter atribut. Modifikasi ini konsisten dengan perangkat tambahan CDI lainnya dalam spesifikasi Java EE 8, seperti konverter JSF canggih, karena sekarang mereka juga dapat menggunakan injeksi CDI.

Untuk memanfaatkan fitur baru ini, cukup masukkan sumber daya CDI di konverter atribut, sesuai kebutuhan. Listing 2 memberikan contoh konverter atribut, dan sekarang saya akan memisahkannya, menjelaskan semua detail penting.

Kelas konverter harus mengimplementasikan antarmuka javax.persistence.AttributeConverter , melewati nilai X dan Y. Nilai X sesuai dengan tipe data dalam objek Java, dan nilai Y harus sesuai dengan jenis kolom database. Kemudian, kelas konverter harus dijelaskan dengan @Converter . Akhirnya, kelas harus mengganti metode convertToDatabaseColumn() dan convertToEntityAttribute() . Implementasi di masing-masing metode ini harus mengkonversi nilai dari tipe tertentu dan kembali ke sana.

Untuk secara otomatis menerapkan konverter setiap kali tipe data yang ditentukan digunakan, tambahkan "otomatis", seperti pada @Converter(autoApply=true) . Untuk menerapkan konverter ke satu atribut, gunakan anotasi @Converter di tingkat atribut, seperti yang ditunjukkan di sini:

 @Convert(converter=LocalDateConverter.java) private LocalDate workDate; 

Konverter juga dapat diterapkan di tingkat kelas:

 @Convert(attributeName="workDate", converter = LocalDateConverter.class) public class Job implements Serializable { . . . 

Misalkan saya ingin mengenkripsi nilai-nilai yang terkandung dalam bidang creditLimit entitas Customer ketika disimpan. Untuk menerapkan proses ini, nilai-nilai harus dienkripsi sebelum disimpan, dan didekripsi setelah diambil dari database. Ini dapat dilakukan oleh konverter dan, menggunakan JPA 2.2, saya dapat menanamkan objek enkripsi di konverter untuk mencapai hasil yang diinginkan. Listing 3 memberikan contoh.

Listing 3

 @Converter public class CreditLimitConverter implements AttributeConverter<BigDecimal, BigDecimal> { @Inject CreditLimitEncryptor encryptor; @Override public BigDecimal convertToDatabaseColumn (BigDecimal entityValue) { String encryptedFormat = encryptor.base64encode(entityValue.toString()); return BigDecimal.valueOf(Long.valueOf(encryptedFormat)); } ... } 

Dalam kode ini, proses dilakukan dengan CreditLimitEncryptor kelas CreditLimitEncryptor ke konverter dan kemudian menggunakannya untuk membantu proses.

Streaming Hasil Permintaan

Sekarang Anda dapat dengan mudah memanfaatkan fitur Java SE 8 stream saat bekerja dengan hasil kueri. Utas tidak hanya menyederhanakan membaca, menulis, dan memelihara kode, tetapi juga membantu meningkatkan kinerja kueri dalam beberapa situasi. Beberapa implementasi utas juga membantu menghindari sejumlah besar permintaan data secara simultan yang sangat besar, meskipun dalam beberapa kasus menggunakan pagination ResultSet mungkin bekerja lebih baik daripada stream.

Untuk mengaktifkan fungsi ini, metode getResultStream() telah ditambahkan ke TypedQuery Query dan TypedQuery . Perubahan kecil ini memungkinkan JPA untuk hanya mengembalikan aliran hasil alih-alih daftar. Jadi, jika Anda bekerja dengan ResultSet besar, masuk akal untuk membandingkan kinerja antara implementasi utas baru dan ResultSets atau pagination yang dapat digulir. Alasannya adalah implementasi utas mengambil semua catatan sekaligus, menyimpannya dalam daftar, dan kemudian mengembalikannya. Teknik ResultSet dan pagination yang dapat digulir mengambil sedikit demi sedikit data, yang mungkin lebih baik untuk set data besar.

Penyedia ketekunan mungkin memutuskan untuk mengganti metode getResultStream() implementasi yang ditingkatkan. Hibernate sudah menyertakan metode stream () yang menggunakan ResultSet dapat digulir untuk mengurai hasil rekaman alih-alih mengembalikannya sepenuhnya. Ini memungkinkan Hibernate untuk bekerja dengan dataset yang sangat besar dan melakukannya dengan baik. Penyedia lain dapat diharapkan untuk mengganti metode ini untuk menyediakan fitur serupa yang bermanfaat bagi JPA.

Selain kinerja, kemampuan untuk mengalirkan hasil adalah tambahan yang bagus untuk JPA, yang menyediakan cara yang nyaman untuk bekerja dengan data. Saya akan menunjukkan beberapa skenario di mana ini mungkin berguna, tetapi kemungkinan itu sendiri tidak terbatas. Dalam kedua skenario, saya meminta entitas Job dan mengembalikan aliran. Pertama, lihat kode berikut, di mana saya hanya menguraikan aliran Jobs terhadap Customer tertentu dengan memanggil metode antarmuka Query getResultStream() . Kemudian, saya menggunakan utas ini untuk menampilkan detail tentang customer dan work date Job'a.

 public void findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery("select object(o) from Job o " + "where o.customer = :customer") .setParameter("customer", customer) .getResultStream(); jobList.map(j -> j.getCustomerId() + " ordered job " + j.getId() + " - Starting " + j.getWorkDate()) .forEach(jm -> System.out.println(jm)); } 


Metode ini dapat sedikit dimodifikasi sehingga mengembalikan daftar hasil menggunakan metode Collectors .toList() sebagai berikut.

 public List<Job> findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery( "select object(o) from Job o " + "where o.customerId = :customer") .setParameter("customer", customer) .getResultStream(); return jobList.collect(Collectors.toList()); } 

Dalam skenario berikut, yang ditunjukkan di bawah ini, saya menemukan List tugas yang terkait dengan kumpulan formulir tertentu. Dalam hal ini, saya mengembalikan semua tugas yang cocok dengan formulir yang dikirimkan sebagai string. Mirip dengan contoh pertama, pertama saya mengembalikan aliran catatan Jobs . Lalu, saya memfilter catatan berdasarkan formulir kumpulan pelanggan. Seperti yang Anda lihat, kode yang dihasilkan sangat kompak dan mudah dibaca.

 public List<Job> findByCustPoolShape(String poolShape){ Stream<Job> jobstream = em.createQuery( "select object(o) from Job o") .getResultStream(); return jobstream.filter( c -> poolShape.equals(c.getCustomerId().getPoolId().getShape())) .collect(Collectors.toList()); } 

Seperti yang saya sebutkan sebelumnya, penting untuk mengingat kinerja dalam skenario di mana sejumlah besar data dikembalikan. Ada kondisi di mana utas lebih berguna dalam kueri basis data, tetapi ada juga di mana mereka dapat menyebabkan penurunan kinerja. Aturan praktis yang baik adalah bahwa jika data dapat ditanyakan sebagai bagian dari query SQL, masuk akal untuk melakukan hal itu. Terkadang, manfaat menggunakan sintaks ulir elegan tidak melebihi kinerja terbaik yang dapat dicapai menggunakan penyaringan SQL standar.

Dukungan Anotasi Duplikat

Ketika Java SE 8 dirilis, duplikat anotasi menjadi mungkin, memungkinkan Anda untuk menggunakan kembali anotasi dalam deklarasi. Beberapa situasi memerlukan penggunaan anotasi yang sama di kelas atau bidang beberapa kali. Misalnya, mungkin ada lebih dari satu penjelasan @SqlResultSetMapping untuk kelas entitas yang diberikan. Dalam situasi di mana dukungan untuk anotasi ulang diperlukan, anotasi wadah harus digunakan. Duplikat anotasi tidak hanya mengurangi persyaratan untuk membungkus koleksi anotasi identik dalam anotasi wadah, tetapi juga dapat membuat kode lebih mudah dibaca.

Ini berfungsi sebagai berikut: implementasi kelas anotasi harus ditandai dengan meta-anotasi @Repeatable untuk menunjukkan bahwa itu dapat digunakan lebih dari sekali. @Repeatable meta @Repeatable mengambil tipe kelas anotasi wadah. Misalnya, NamedQuery anotasi NamedQuery sekarang ditandai dengan @Repeatable(NamedQueries.class) . Dalam hal ini, anotasi wadah masih digunakan, tetapi Anda tidak harus memikirkannya saat menggunakan anotasi yang sama pada deklarasi atau kelas, karena @Repeatable mengabstraksikan detail ini.

Kami memberi contoh. Jika Anda ingin menambahkan lebih dari satu penjelasan @NamedQuery ke kelas entitas di JPA 2.1, Anda perlu merangkumnya di dalam penjelasan @NamedQueries , seperti yang ditunjukkan pada Listing 4.

Listing 4

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQueries({ @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") , @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") , @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . .)}) public class Customer implements Serializable { . . . } 

Namun, dalam JPA 2.2, semuanya berbeda. Karena @NamedQuery adalah anotasi duplikat, ini dapat ditentukan dalam kelas entitas lebih dari sekali, seperti yang ditunjukkan pada Listing 5.

Listing 5

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . . public class Customer implements Serializable { . . . } 

Daftar duplikat anotasi:

  • @AssociationOverride
  • @AttributeOverride
  • @Convert
  • @JoinColumn
  • @MapKeyJoinColumn
  • @NamedEntityGraphy
  • @NamedNativeQuery
  • @NamedQuery
  • @NamedStoredProcedureQuery
  • @PersistenceContext
  • @PersistenceUnit
  • @PrimaryKeyJoinColumn
  • @SecondaryTable
  • @SqlResultSetMapping

Kesimpulan

Versi JPA 2.2 memiliki beberapa perubahan, tetapi peningkatan yang disertakan cukup signifikan. Akhirnya, JPA disejajarkan dengan Java SE 8, memungkinkan pengembang untuk menggunakan fitur-fitur seperti Date and Time API, streaming hasil kueri, dan pengulangan anotasi. Rilis ini juga meningkatkan konsistensi CDI dengan menambahkan kemampuan untuk menanamkan sumber daya CDI di konverter atribut. JPA 2.2 sekarang tersedia dan merupakan bagian dari Java EE 8, saya pikir Anda akan suka menggunakannya.

AKHIR

Seperti biasa, kami menunggu pertanyaan dan komentar.

Source: https://habr.com/ru/post/id423195/


All Articles