माझ्या Best Practices – Java
1. Loop मध्ये String.replaceAll() ऐवजी Pattern.compile()
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Pattern नेहमी आधी compile करावा लागतो. तोच Pattern छोट्या strings वर वारंवार चालत असेल, तर compiled Pattern पुन्हा वापरल्यास performance बरीच
सुधारते.
2. Loop मध्ये + ऐवजी StringBuilder
Loop मध्ये + ऐवजी new StringBuilder() अधिक performant आहे.
3. equals() आणि hashCode() योग्य प्रकारे वापरा
कधी कधी equals() किंवा hashCode() मध्ये सर्व fields समाविष्ट केले जातात. एखादी entity बदलली आहे का, हे तपासण्यासाठी हे उपयोगी असले, तरी ते चुकीचे आहे.
equals() चा उद्देश entity modify झाली आहे का हे तपासणे नसून, object instance वेगळा असला तरीही ती तीच entity आहे का, हे ठरवणे हा आहे.
म्हणजेच: फक्त primary key वापरा!
याची गरज सर्व प्रकारच्या collections मध्ये पडते. List वर .contains() call केल्यास Java equals method वापरते. HashMaps ना याशिवाय hashCode() सुद्धा लागतो.
या दोन्ही नेहमी एकत्रच override कराव्यात.
ID नसलेल्या, अजून save न झालेल्या entities हा एक नेहमीचा pitfall आहे. इथे सावध राहा आणि अडचणी टाळण्यासाठी लवकर persist करा.
हेही पाहा:
या methods च्या contract चे नेहमी पालन व्हायला हवे:
4. वेगवेगळ्या operating systems एकत्र वापरताना DateTime चे pitfalls
काही operating systems चे internal clock nanoseconds मध्ये चालते, तर काहींचे milliseconds मध्ये. त्यामुळे ZonedDateTime.now() दोन्ही platforms वर सारखे काम करत नाही आणि
equals() बिघडवते. त्याऐवजी नेहमी precision adjust करा!
/**
* truncating the precision, because it relies on the internal clock of linux/macOs/windows.
* windows and older macOS versions use only milliseconds, whereas linux and new macOS versions use microsecond precision.
*/
public static ZonedDateTime getNow() {
return ZonedDateTime.now().truncatedTo(DateTimeUtil.PRECISION);
}
खालील JPA annotations सुद्धा ZonedDateTime.now() वर अवलंबून असतात आणि ते कधीही जसेच्या तसे (plain) वापरू नयेत:
@CreationTimestamp@UpdateTimestamp@PastOrPresent
5. Complex data types inline declare करू नका
Complex data types साठी Pair, Lists वगैरे एकमेकांत nest करण्याऐवजी नेहमी स्वतंत्र classes तयार करा.
वाईट:
// unlesbar
public Triple<List<Pair<List<List<ChatMessage>>, ZonedDateTime>>, Language, ChatRoom> getChatMessagesGroupedByDay();
अधिक चांगले:
public class FTOChatMessageCollection {
private List<FTOChatMessageCollectionPerDay> messagesPerDay;
private Language language;
private ChatRoom chatRoom;
// constructor, getter & setter ...
}
public class FTOChatMessageCollectionPerDay {
private List<FTOChatMessage> messages;
private ZonedDateTime day;
// constructor, getter & setter ...
}
फायदे:
getFirst()आणिgetSecond()पेक्षा चांगली नावे- Getters आणि setters मुळे वापर स्पष्टपणे ठरतो (class immutable आहे का? कोणत्या list मध्ये नवीन items जोडता येतात? Object instantiate कसा करायचा?) → Encapsulation कोडचा वापर आपोआप document करते
- वाचायला कितीतरी सोपे
अपवाद:
साध्या relations साठी Pair उत्तम आहे! उदाहरणार्थ:
Pair<User, Training> pair = this.addCurrentUserToTraining(training);
6. Caching – पण योग्य पद्धतीने (Minimal Setup Guide)
Cache (उच्चार /kæʃ/, “cash” सारखा) म्हणजे मुळात entries साठवणारा एक Map. वेळेवर आधारित invalidation सारख्या अधिक गुंतागुंतीच्या गोष्टी इथे मुद्दाम टाळल्या आहेत.
साधा Map पुरेसा असेल, तर प्रत्येक वेळी library ची गरज नसते! बऱ्याच डेव्हलपर्सना हे समजत नाही आणि भीतीपोटी ते सरसकट libraries वापरतात. Caching मधली सर्वात महत्त्वाची गोष्ट म्हणजे invalidation योग्य वेळी होणे.
पहिला धोका: Memory Leaks
Caching करताना memory leaks अगदी सहज होतात. equals() आणि hashCode() नीट override करायला विसरलात, की लगेच HashMap memory leak करू लागतो.
नेहमी safe ठरणारी एक पद्धत म्हणजे map keys स्वतः हाताने तयार करणे. फक्त entity ची ID समाविष्ट करणे उत्तम, नाहीतर एखादे field बदलताच map leak होऊ लागतो.
आणि तेच बरोबरही आहे, कारण एखाद्या user चे first name बदलले म्हणून तो दुसरा user होत नाही. तुलना करा: 3. equals() आणि hashCode()
योग्य प्रकारे वापरा
दुसरा धोका: Thread Safety
Cache साठी ConcurrentHashMap वापरणे सर्वोत्तम, कारण तो atomic, thread-safe operations देतो. Map फक्त एकाच call ने – computeIfAbsent() – modify केला जातो, आणि त्यामुळेच thread safety साधली जाते:
private ConcurrentMap<String, T> cache = new ConcurrentHashMap<>();
protected T getOrCompute(final String key, final Supplier<T> computeFunction) {
// thread safe, because `computeIfAbsent` is atomic, so no need for `synchronized` here.
// if you extend this to more than 1 map operation, you should synchronize it
return this.getCacheMap().computeIfAbsent(key, k -> computeFunction.get());
}
कोड जास्त लांब असता आणि त्यात अनेक calls असते, तर संपूर्ण method synchronized करावी लागली असती.
7. Serialization – त्याची अजूनही गरज आहे का?
माझा प्रश्न: एक Sonar rule आहे, ज्याचे नाव Fields in a “Serializable” class should either be transient or serializable आहे (म्हणजे “Serializable” class मधील fields एकतर transient असावेत किंवा serializable). तुम्ही तुमच्या class मध्ये एखादे non-serializable field जोडता, तेव्हा हा rule समोर येतो. Rule असे सांगतो:
उदाहरणार्थ, load असताना बहुतेक J2EE application frameworks objects disk वर flush करतात, आणि non-transient, non-serializable data members असलेला एखादा तथाकथित Serializable object program crash करू शकतो आणि attackers साठी दार उघडू शकतो.
मला हे कालबाह्य वाटते. Effective Java 3rd Edition मध्ये म्हटले आहे की नवीन software तयार करताना Serialization ला महत्त्व उरलेले नाही. शिवाय, Java मधून Serialization काढून टाकणे हे Oracle चे ‘long-term goal’ आहे. खुद्द Java चे chief architect, Mark Reinhold म्हणाले होते:
“Serialization ही 1997 मध्ये झालेली एक भयंकर चूक होती.”
Spring framework objects flush करू शकते का, हा प्रश्न इथे आधीच विचारला गेला आहे:
म्हणून माझा प्रश्न असा:
एखादा आधुनिक application server, framework किंवा स्वतः J2EE implementation (विशेषतः CDI, EJB, JPA सोबत काम करणारे Java components) जास्त load असताना objects disk वर flush करतो का?
की हे विधान आता कालबाह्य झाले आहे?
उत्तर:
TL;DR तुम्ही stateful distributable web application बनवत नसाल, session persistence enable करत नसाल आणि फक्त in-memory caches वापरत असाल, तर कदाचित तुम्हाला कुठेही Serializable ची गरज पडणार नाही.
स्पष्टीकरण:
Session replication साठी नवीनतम Servlet Specification मध्येही Java Serialization अजूनही अनिवार्य आहे: https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#distributed-environments
एक नेहमीचा use case: load balancer च्या मागे अनेक servers असतील आणि तुम्ही एका server वर login केले, तर तुम्हाला एक JSESSIONID cookie मिळते. पुढच्या request साठी load balancer
तुम्हाला दुसऱ्या server वर पाठवत असेल, तर तुमचे login attributes आणि इतर session-scoped attributes serialize होऊन नवीन server वर replicate होतात, जेणेकरून तुमचा JSESSIONID तिथेही valid राहील.
Session-scoped attributes साठी Serialization लागणारा आणखी एक use case म्हणजे application server ने sessions disk वर swap करण्याचा निर्णय घेणे. उदाहरणार्थ, Tomcat 8 मध्ये हे असे configure करता येते की memory मध्ये ठेवण्यासाठी sessions खूप जास्त झाल्यावर किंवा server restart होताना sessions टिकवून ठेवायचे असतील, तेव्हा ते activate होईल. पाहा https://tomcat.apache.org/tomcat-9.0-doc/config/manager.html
अशा use cases साठी प्रत्येक Servlet-5.0 compatible application server ने HTTP sessions मधील Serializable objects ना default स्वरूपात support करणे आवश्यक आहे.
काही servers वर हे बदलता किंवा override करता येते, उदा. Jackson-JSON serialization किंवा तत्सम काहीतरी वापरून. उदाहरणार्थ पाहा https://stackoverflow.com/questions/46459707/how-to-use-jackson-instead-of-jdkserializationredisserializer-in-spring
आणखी एक नेहमीचा use case म्हणजे caching. Objects शक्य तितक्या वेगाने उपलब्ध ठेवण्यासाठी याचा वापर होतो – आदर्शपणे memory मध्ये, पण गरज पडल्यास तात्पुरते disk वर किंवा एखाद्या external cache server वर offload करून.
यासाठी एक standard API (JSR-107) आहे, ज्याने जाणीवपूर्वक Serializable objects अनिवार्य केले नाहीत, पण बहुतेक implementations offloading च्या भागासाठी default म्हणून अजूनही Java Serialization च वापरतात.
इतर serialization mechanisms ना support करण्यासाठी हेही बदलता येतात, उदा. ehcache साठी: https://www.ehcache.org/documentation/3.8/serializers-copiers.html
8. Hibernate: Fetching strategy
EAGER fetching is a code smell लक्षात घेता – जो लेख Hibernate contributor असलेल्या एका कमालीच्या करिष्माई
अतिशहाण्याने लिहिला आहे – FetchType म्हणून LAZY ला प्राधान्य द्यावे. काही data types foreign key शिवाय अर्थहीन असले, तरीही हे types स्वतंत्रपणे
query केले जातातच (getCount(), data tables, वगैरे).
खाली सर्व scenarios उपाय किती समाधानकारक आहे त्या क्रमाने दिले आहेत – सर्वात कमी समाधानकारकापासून सुरुवात करून:
1. सर्वात वाईट स्थिती: LazyInitializationException
शब्दांची गरज नाही. ही वेदना आपण सगळेच जाणतो.
2. तात्पुरता जुगाड (quick fix)
public void updateChatRoom(ChatMessage message) {
// reload here or LazyInitializationException would be thrown
message = this.chatMessageService.reload(message); // not optimal: second roundtrip to the database - could easily take 100ms!
// we missed the chance to unproxy without a performance hit.
ChatRoom chatRoom = message.getChatRoom().getUpdatedAt(DateTimeUtil.getNow());
}
3. Service मध्येच थेट unproxy करा
public ChatMessage getChatMessage(final Long id, boolean withRoom) {
// assume this is the same service + the same transaction.
// so we are in the same persistence context + everything will be performed together at the transaction flush.
// perfect: no additional roundtrip like in example 2.!
ChatMessage message = this.chatMessageService.getById(id);
if (withRoom) {
ChatRoom chatRoom = this.chatMessageService.unproxy(message.getChatRoom());
}
// we should not call message.setChatRoom() -> this getter should not even exist as Hibernate would
// map this relation for us! setting it again would perform an update on our foreign key!!
// so we have no option to set the ChatRoom back to the message and have to pass it everywhere in a Tuple or something similar
return ???;
}
4. Service मध्येच थेट fetch करा (हेच करा!)
public ChatMessage getChatMessage(final Long id, boolean withRoom) {
// all aforementioned problems solved
return this.executeSingleResultQuery((query, cb, root) -> {
if (withRoom) {
root.fetch(ChatRoomEntity_.partition).fetch(ChatPartitionEntity_.chatModule);
}
return cb.equal(root.get(ChatRoomEntity_.id), id);
});
}
9. Hibernate Search
Hibernate Search हे Hibernate JPA शी पूर्णपणे जोडलेले आहे. Entities बदलल्या की त्याला कळते, आणि ते सहज configure करता येते:
@Entity
@Indexed(interceptor = SearchableChatMessageInterceptor.class) // hier können Einträge von der Indexierung ausgeschlossen werden
@FullTextFilterDef(name = "SearchableChatMessageFilter", impl = SearchableChatMessageFilter.class) // hier wird der Key festgelegt
public class ChatMessageEntity implements Searchable { // das Searchable dient als Marker-Interface
@Column
@Field // dieses Feld wird indexiert
private String message;
@ManyToOne(targetEntity = ChatRoomEntity.class, fetch = FetchType.LAZY, optional = false)
@IndexedEmbedded(includeEmbeddedObjectId = true, targetElement = ChatRoomEntity.class) // indexiert den Fremschlüssel mit.
// Über dieses Entity wird auch die Message gesucht,
// wie in @FullTextFilterDef -> SearchableChatMessageFilter definiert
private ChatRoom chatRoom;
// ...
}
Local environment मध्ये search बंद असतो:
// DefaultInstanceInitializer.java
// we don't need the global search everywhere, givin an option to override this setting here
protected boolean isBuildSearchIndexes() {
return !Constants.Mode.isDebug();
}
Fuzzy search आणि search चे वर्तन
if ((needle.split(" ").length > 1 || needle.contains("\"")) && !searchExact) {
/*
* Simple Query String. Provides basic functionality like:
*
* boolean (AND using “+”, OR using “|”, NOT using “-“)
* prefix (prefix*)
* phrase (“some phrase”)
* precedence (using parentheses)
* fuzzy (fuzy~2)
* near operator for phrase queries (“some phrase”~3)
*/
query = qb.simpleQueryString()
.onField(field)
.andFields(moreFields)
.withAndAsDefaultOperator()
.matching(needle)
.createQuery();
} // ...
Lucene
Luke वापरून Lucene debug करणे
आमचे Hibernate Search version Luke version 6.6.0 वापरूनच debug करावे लागते. ते फक्त याच version सोबत काम करते!
- डाउनलोड: https://github.com/DmitryKey/luke/releases
- याच्या मदतीने
target/फोल्डरमधील indexes उघडता येतात. - Entities मुळात योग्य प्रकारे index होत आहेत का, हे पाहण्याचा हा चांगला मार्ग आहे.
- Luke मधूनही search चालवता येतो.
ElasticSearch
Hibernate Search हे ElasticSearch लाही support करते (तेव्हा ते Lucene ऐवजी वापरले जाते आणि त्यात अधिक चांगला fuzzy search / अधिक Google सारखा अनुभव मिळतो).