నా 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 చిన్న Stringలపై తరచూ నడిస్తే, compile చేసిన Patternను
మళ్లీ వాడితే performance గణనీయంగా మెరుగవుతుంది.
2. Loopలో +కు బదులు StringBuilder
Loopలో + కంటే new StringBuilder() ఎక్కువ performance ఇస్తుంది.
3. equals(), hashCode()లను సరిగ్గా వాడటం
కొన్నిసార్లు equals() లేదా hashCode()లో అన్ని fieldలనూ చేరుస్తారు. ఒక entity మారిందో లేదో తనిఖీ చేయడానికి ఇది ఉపయోగకరంగా అనిపిస్తుంది, కానీ ఇది తప్పు.
equals() ఉద్దేశం ఒక entity మారిందో లేదో తనిఖీ చేయడం కాదు; object instance వేరైనా అది అదే entityనా కాదా అని నిర్ణయించడం.
అంటే: Primary keyని మాత్రమే వాడండి!
అన్ని రకాల Collectionలకూ ఇది అవసరం. ఒక Listపై .contains()ను పిలిస్తే, Java equals methodను వాడుతుంది. HashMapలకు అదనంగా hashCode() కూడా కావాలి.
రెండింటినీ కలిపే override చేయాలి.
ID లేని, ఇంకా save చేయని entityలు ఒక సాధారణ ఉచ్చు. ఇక్కడ జాగ్రత్తగా ఉండండి; సమస్యలను నివారించడానికి ముందుగానే persist చేయండి.
పోల్చి చూడండి:
ఈ methodల contractను ఎప్పుడూ పాటించాలి:
4. వేర్వేరు operating systemలను కలిపి వాడినప్పుడు DateTime ఉచ్చులు
కొన్ని operating systemల అంతర్గత గడియారం nanosecondలలో, మరికొన్నిటిది millisecondలలో నడుస్తుంది. దీనివల్ల ZonedDateTime.now() రెండు platformలలోనూ ఒకేలా పనిచేయదు;
equals()ను కూడా పాడుచేస్తుంది. కాబట్టి ఎల్లప్పుడూ precisionను సరిచేయండి!
/**
* 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 methodలు కూడా ZonedDateTime.now() మీద ఆధారపడతాయి; వాటిని ఎప్పుడూ నేరుగా (plain) వాడకూడదు:
@CreationTimestamp@UpdateTimestamp@PastOrPresent
5. సంక్లిష్ట data typeలను inlineగా ప్రకటించకండి
Pair, List వంటివాటిని ఒకదానిలో ఒకటి పేర్చే బదులు, సంక్లిష్ట data typeల కోసం ఎప్పుడూ ప్రత్యేక classలను సృష్టించండి.
చెడ్డది:
// 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()కంటే మెరుగైన పేర్లు- Getter, setterల ద్వారా వాడకం స్పష్టంగా నిర్వచించబడుతుంది (class immutableనా? ఏ listకు కొత్త అంశాలు జోడించవచ్చు? Objectను ఎలా సృష్టించాలి?) → Encapsulation కోడ్ను ఎలా వాడాలో తనంతట తానే డాక్యుమెంట్ చేస్తుంది
- చాలా మెరుగైన పఠనీయత
మినహాయింపులు:
సరళమైన సంబంధాలకు Pair అద్భుతం! ఉదాహరణకు:
Pair<User, Training> pair = this.addCurrentUserToTraining(training);
6. Caching – కానీ సరైన పద్ధతిలో (కనీస setup గైడ్)
Cache (ఉచ్చారణ /kæʃ/, ఇంగ్లీష్ “cash” లాగా) ప్రాథమికంగా entryలను నిల్వ చేసే ఒక Map మాత్రమే. సమయం ఆధారిత invalidation వంటి సంక్లిష్ట విషయాలను ఇక్కడ ఉద్దేశపూర్వకంగా వదిలేశాం.
ఒక సాధారణ Map సరిపోయే చోట ఎప్పుడూ library అవసరం లేదు! చాలా మంది developerలు ఇది అర్థం చేసుకోరు; భయంతో సూత్రప్రాయంగానే libraryలను వాడతారు. Cachingలో అత్యంత ముఖ్యమైన అంశం సరైన సమయంలో invalidate చేయడం.
ఉచ్చు #1: Memory Leak
Cache చేసేటప్పుడు Memory Leak చాలా సులభంగా జరగవచ్చు. equals(), hashCode()లను సరిగ్గా override చేయడం మర్చిపోతే చాలు – మీ HashMap memoryని
leak చేయడం మొదలుపెడుతుంది. ఎప్పుడూ సురక్షితమైన ఒక పద్ధతి: Map keyలను చేతితో సృష్టించడం. Entity IDని మాత్రమే చేర్చడం ఉత్తమం; లేకపోతే ఒక్క field మారినా
Map leak అవుతుంది. అదే సరైనది కూడా, ఎందుకంటే ఒక User మొదటి పేరు మారినా అతను అదే User. పోల్చండి: 3. equals(), hashCode()లను
సరిగ్గా వాడటం
ఉచ్చు #2: Thread Safety
Cacheకు ఉత్తమమైనది ఒక ConcurrentHashMap; అది atomic, thread-safe operationలను అందిస్తుంది. Map ఒకే ఒక్క call ద్వారా మాత్రమే మారుతుంది – computeIfAbsent() – దీనితో 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());
}
కోడ్ ఇంకా పొడవుగా ఉండి, అనేక callలు ఉంటే, method మొత్తాన్ని synchronized చేయాల్సి ఉంటుంది.
7. Serialization – ఇంకా అవసరమా?
నా ప్రశ్న: ఒక Sonar నియమం ఉంది, దాని పేరు Fields in a “Serializable” class should either be transient or serializable. మీ classకు serializable కాని ఒక fieldను జోడించినప్పుడు ఇది కనిపిస్తుంది. ఆ నియమం ఇలా చెబుతుంది:
ఉదాహరణకు, ఎక్కువ load ఉన్నప్పుడు చాలా J2EE application frameworkలు objectలను diskకు flush చేస్తాయి; transient కాని, serializable కాని data memberలు ఉన్న, Serializable అని చెప్పుకునే ఒక object program crashలకు కారణం కావచ్చు, దాడి చేసేవారికి తలుపులు తెరవవచ్చు.
ఇది నాకు పాతబడిన మాటగా అనిపిస్తోంది. కొత్త సాఫ్ట్వేర్ అభివృద్ధి చేసేటప్పుడు Serialization సంబంధితం కాదని Effective Java 3rd Edition చెబుతోంది. అంతేకాక, Java నుండి Serializationను తొలగించడం Oracle యొక్క ‘దీర్ఘకాలిక లక్ష్యం’. Java చీఫ్ ఆర్కిటెక్ట్ Mark Reinhold స్వయంగా ఇలా అన్నారు:
“1997లో Serialization ఒక భయంకరమైన తప్పు.”
Spring framework objectలను flush చేయగలదా అనే ప్రశ్న ఇక్కడ ఇప్పటికే అడిగారు:
కాబట్టి నా ప్రశ్న:
ఆధునిక application serverలు, frameworkలు లేదా J2EE implementation స్వయంగా (ముఖ్యంగా CDI, EJB, JPAతో పనిచేసే Java componentలు) ఎక్కువ load ఉన్నప్పుడు objectలను diskకు flush చేస్తాయా?
లేక ఈ మాట పాతబడిపోయిందా?
సమాధానం:
TL;DR మీరు stateful distributable web application తయారు చేయకపోతే, session persistenceను ఆన్ చేయకపోతే, in-memory cacheలను మాత్రమే వాడితే, ఎక్కడా Serializable లేకుండానే గడిపేయవచ్చు.
వివరణ:
Session replication కోసం, తాజా Servlet Specificationలో కూడా Java Serialization ఇంకా తప్పనిసరి: https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#distributed-environments
ఒక సాధారణ సందర్భం: ఒక load balancer వెనుక అనేక serverలు ఉన్నాయి; మీరు ఒక serverలో login అయితే మీకు ఒక JSESSIONID cookie వస్తుంది. తర్వాతి requestలో
load balancer మిమ్మల్ని వేరే serverకు పంపితే, మీ login attributeలు, ఇతర session-scoped attributeలు serialize అయి కొత్త serverలో replicate అవుతాయి; దాంతో అక్కడ కూడా మీ JSESSIONID చెల్లుబాటు అవుతుంది.
మీ session-scoped attributeలకు Serialization అవసరమయ్యే మరో సందర్భం: application server sessionలను diskకు swap చేయాలని నిర్ణయించినప్పుడు. ఉదాహరణకు Tomcat 8లో, memoryలో ఉంచలేనన్ని sessionలు ఉన్నప్పుడు, లేదా server restart అయినా sessionలను సజీవంగా ఉంచాలనుకున్నప్పుడు ఇది పనిచేసేలా configure చేయవచ్చు. చూడండి: https://tomcat.apache.org/tomcat-9.0-doc/config/manager.html
ఇలాంటి సందర్భాల కోసం, Servlet-5.0కు అనుగుణంగా ఉండే ప్రతి application server డిఫాల్ట్గా Http Sessionలలో Serializable objectలను సపోర్ట్ చేయాలి.
కొన్ని serverలలో దీన్ని మార్చవచ్చు లేదా override చేయవచ్చు – ఉదాహరణకు Jackson-JSON serialization లేదా అలాంటిదేదైనా వాడి. ఉదాహరణకు చూడండి: https://stackoverflow.com/questions/46459707/how-to-use-jackson-instead-of-jdkserializationredisserializer-in-spring
మరో సాధారణ సందర్భం caching. Objectలను వీలైనంత వేగంగా అందుబాటులో ఉంచడానికి ఇది వాడతారు – ఆదర్శంగా memoryలో, కానీ అవసరమైతే తాత్కాలికంగా diskకు లేదా ఒక బయటి cache serverకు తరలించి.
దీనికి ఒక standard API (JSR-107) ఉంది; అది ఉద్దేశపూర్వకంగా Serializable objectలను తప్పనిసరి చేయలేదు, అయినా చాలా implementationలు offloading భాగానికి డిఫాల్ట్గా Java Serializationనే వాడతాయి.
వీటిని కూడా ఇతర serialization పద్ధతులను సపోర్ట్ చేసేలా మార్చవచ్చు, ఉదాహరణకు ehcache కోసం: https://www.ehcache.org/documentation/3.8/serializers-copiers.html
8. Hibernate: Fetching వ్యూహం
EAGER fetching is a code smell అనే వ్యాసాన్ని దృష్టిలో ఉంచుకుంటే – దాన్ని రాసింది అమితమైన ఆకర్షణ గల ఒక సర్వజ్ఞుడు, పైగా Hibernate
contributor కూడా – FetchTypeగా LAZYను ఎంచుకోవడమే మేలు. కొన్ని data typeలు foreign key లేకుండా అర్థరహితమైనా, ఆ typeలను విడిగా కూడా
query చేస్తారు (getCount(), Data-Tableలు మొదలైనవి).
పరిష్కారం ఎంత సంతృప్తికరంగా ఉందో ఆ క్రమంలో అన్ని సందర్భాలూ కింద ఉన్నాయి – అతి తక్కువ సంతృప్తికరమైనదానితో మొదలుపెట్టి:
1. అత్యంత చెడ్డ పరిస్థితి: LazyInitializationException
ఇంకేమీ చెప్పనక్కర్లేదు. ఈ బాధ మనందరికీ తెలిసిందే.
2. తాత్కాలిక పరిష్కారం
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తో సజావుగా అనుసంధానమై ఉంటుంది. Entityలు మారినప్పుడు అది గమనిస్తుంది; సులభంగా 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 వాతావరణంలో 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 & సెర్చ్ ప్రవర్తన
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 వెర్షన్ను Luke 6.6.0 వెర్షన్తోనే debug చేయాలి. ఇది ఈ వెర్షన్తో మాత్రమే పనిచేస్తుంది!
- డౌన్లోడ్: https://github.com/DmitryKey/luke/releases
- దీనితో
target/folderలోని indexలను తెరవవచ్చు. - Entityలు అసలు సరిగ్గా index అవుతున్నాయో లేదో చూడటానికి ఇది మంచి చోటు.
- Luke నుండే ఒక searchను కూడా ప్రారంభించవచ్చు.
ElasticSearch
Hibernate Search, ElasticSearchను కూడా సపోర్ట్ చేస్తుంది (అప్పుడు అది Luceneకు బదులుగా వాడబడుతుంది; దాని fuzzy search మెరుగ్గా ఉంటుంది / Google లాంటి అనుభూతి ఎక్కువ).