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.2JPA 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 8Mungkin 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 TertanamKonverter 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 PermintaanSekarang 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 DuplikatKetika 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
KesimpulanVersi 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.