میرے بہترین طریقے – Java
1. لوپ میں String.replaceAll() کے بجائے Pattern.compile()
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Pattern کو ہمیشہ پہلے کمپائل کرنا پڑتا ہے۔ اگر وہی Pattern بار بار چھوٹی strings پر چلے، تو کمپائل شدہ Pattern کو دوبارہ استعمال کرنے سے
پرفارمنس کہیں بہتر ہوتی ہے۔
2. لوپ میں + کے بجائے StringBuilder
لوپ میں + کے بجائے new StringBuilder() زیادہ تیز ہے۔
3. equals() اور hashCode() کا درست استعمال
بعض اوقات equals() یا hashCode() میں تمام فیلڈز شامل کر لیے جاتے ہیں۔ یہ جانچنے کے لیے تو مفید ہے کہ کوئی Entity بدلی ہے یا نہیں، لیکن یہ غلط ہے۔
equals() کا مقصد یہ جانچنا نہیں کہ کوئی Entity تبدیل ہوئی ہے، بلکہ یہ طے کرنا ہے کہ – چاہے آبجیکٹ انسٹینس ایک نہ ہو – پھر بھی بات اسی Entity کی ہو رہی ہے
یا نہیں۔ یعنی: صرف پرائمری کی (Primary Key) استعمال کریں!
یہ ہر قسم کی Collections کے لیے ضروری ہے۔ اگر آپ کسی List پر .contains() کال کریں تو Java equals میتھڈ استعمال کرے گی۔ HashMaps کو اس کے علاوہ hashCode() بھی چاہیے۔
دونوں کو ایک ساتھ override کرنا چاہیے۔
ایک عام غلطی وہ غیر محفوظ شدہ Entities ہیں جن کی ID نہیں ہوتی۔ یہاں محتاط رہیں اور مسائل سے بچنے کے لیے جلد persist کریں۔
موازنہ:
ان میتھڈز کے کنٹریکٹ کی ہمیشہ پابندی کرنی چاہیے:
4. مختلف آپریٹنگ سسٹمز کے ملاپ میں DateTime کے مسائل
کچھ آپریٹنگ سسٹمز کی اندرونی گھڑی نینو سیکنڈ تک درست ہوتی ہے، کچھ کی ملی سیکنڈ تک۔ اس لیے ZonedDateTime.now() دونوں پلیٹ فارمز پر ایک جیسا کام نہیں کرتا اور
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 میتھڈز بھی ZonedDateTime.now() پر انحصار کرتے ہیں اور انہیں کبھی جوں کا توں استعمال نہیں کرنا چاہیے:
@CreationTimestamp@UpdateTimestamp@PastOrPresent
5. پیچیدہ ڈیٹا ٹائپس کو اِن لائن ڈکلیئر نہ کریں
پیچیدہ ڈیٹا ٹائپس کے لیے Pair، Lists وغیرہ کو ایک دوسرے میں گھسانے کے بجائے ہمیشہ الگ کلاسز بنائیں۔
برا:
// 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 کے ذریعے واضح طور پر طے ہو جاتا ہے (کیا کلاس immutable ہے؟ کون سی فہرست بڑھائی جا سکتی ہے؟ آبجیکٹ کیسے بنایا جائے؟) ← انکیپسولیشن (Encapsulation) کوڈ کے استعمال کو خود بخود دستاویزی بنا دیتی ہے
- پڑھنے میں کہیں زیادہ آسانی
استثنائات:
سادہ تعلقات کے لیے Pair بہترین ہے! مثال کے طور پر:
Pair<User, Training> pair = this.addCurrentUserToTraining(training);
6. کیشنگ – مگر درست طریقے سے (کم سے کم سیٹ اپ گائیڈ)
کیش (Cache، تلفظ /kæʃ/، “cash” کی طرح) بنیادی طور پر بس ایک Map ہے جو اندراجات رکھتا ہے۔ وقت پر مبنی invalidation جیسی پیچیدہ چیزوں کو چھوڑ دیا گیا ہے۔
جب ایک سادہ Map کافی ہو تو ہر بار لائبریری ضروری نہیں! بہت سے ڈویلپرز یہ نہیں سمجھتے اور ڈر کے مارے اصولاً لائبریریاں لے لیتے ہیں۔ کیشنگ میں سب سے اہم پہلو یہ ہے کہ invalidation صحیح وقت پر ہو۔
خطرہ نمبر 1: میموری لیکس (Memory Leaks)
کیشنگ میں میموری لیکس بہت آسانی سے ہو سکتے ہیں۔ جیسے ہی آپ equals() اور hashCode() کو ٹھیک سے override کرنا بھولے، آپ کے پاس ایک ایسا HashMap ہوگا
جو میموری لیک کرتا ہے۔ ایک طریقہ جو ہمیشہ محفوظ ہے، Map کیز کو ہاتھ سے بنانا ہے۔ بہتر یہ ہوگا کہ صرف Entity کی ID شامل کی جائے، ورنہ کسی ایک فیلڈ کے بدلنے پر ہی
Map لیک ہونے لگتا ہے۔ اور یہ درست بھی ہے، کیونکہ کسی یوزر کا پہلا نام بدلنے سے وہ کوئی دوسرا یوزر نہیں بن جاتا۔ موازنہ کریں: 3. equals() اور hashCode()
کا درست استعمال
خطرہ نمبر 2: تھریڈ سیفٹی (Thread Safety)
کیش بہترین صورت میں ConcurrentHashMap استعمال کرتا ہے، جو atomic اور thread-safe آپریشنز فراہم کرتا ہے۔ Map صرف ایک ہی کال – computeIfAbsent() – کے ذریعے تبدیل ہوتا ہے، اور اسی سے تھریڈ سیفٹی حاصل ہوتی ہے:
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());
}
اگر کوڈ لمبا ہوتا اور اس میں کئی کالز ہوتیں، تو پورے میتھڈ کو synchronized کرنا پڑتا۔
7. سیریلائزیشن – کیا اب بھی اس کی ضرورت ہے؟
میرا سوال: ایک Sonar رول ہے جس کا عنوان ہے “Serializable” کلاس کے فیلڈز یا تو transient ہونے چاہییں یا serializable۔ یہ تب سامنے آتا ہے جب آپ اپنی کلاس میں کوئی non-serializable فیلڈ شامل کرتے ہیں۔ رول کہتا ہے:
مثال کے طور پر، لوڈ کے دوران زیادہ تر J2EE ایپلیکیشن فریم ورکس آبجیکٹس کو ڈسک پر flush کر دیتے ہیں، اور ایک بظاہر Serializable آبجیکٹ جس کے ڈیٹا ممبرز non-transient اور non-serializable ہوں، پروگرام کے کریش ہونے کا سبب بن سکتا ہے اور حملہ آوروں کے لیے دروازہ کھول سکتا ہے۔
مجھے یہ پرانی بات لگتی ہے۔ Effective Java 3rd Edition میں کہا گیا ہے کہ نیا سافٹ ویئر بناتے وقت سیریلائزیشن اہم نہیں۔ نیز، Java سے سیریلائزیشن ہٹانا Oracle کا “طویل مدتی ہدف” ہے۔ خود Java کے چیف آرکیٹیکٹ Mark Reinhold نے کہا:
“سیریلائزیشن 1997 میں ایک خوفناک غلطی تھی۔”
یہ سوال کہ کیا Spring فریم ورک آبجیکٹس کو flush کر سکتا ہے، یہاں پہلے ہی پوچھا جا چکا ہے:
تو میرا سوال یہ ہے:
کیا کوئی جدید ایپلیکیشن سرور، فریم ورک یا خود J2EE کا نفاذ (خاص طور پر وہ Java کمپوننٹس جو CDI، EJB اور JPA کے ساتھ کام کرتے ہیں) بھاری لوڈ میں آبجیکٹس کو ڈسک پر flush کرتا ہے؟
یا یہ بیان پرانا ہو چکا ہے؟
جواب:
خلاصہ: اگر آپ کوئی stateful تقسیم شدہ (distributed) ویب ایپلیکیشن نہیں بنا رہے، سیشن پرسسٹنس فعال نہیں کرتے، اور صرف ان میموری کیشز استعمال کرتے ہیں، تو شاید آپ کہیں بھی Serializable کی ضرورت کے بغیر کام چلا لیں۔
وضاحت:
سیشن ریپلیکیشن کے لیے تازہ ترین Servlet اسپیسیفیکیشن میں Java سیریلائزیشن اب بھی لازمی ہے: https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#distributed-environments
ایک عام صورت یہ ہے کہ آپ کے پاس لوڈ بیلنسر کے پیچھے کئی سرورز ہوں، آپ ایک سرور پر لاگ اِن کریں اور آپ کو JSESSIONID کوکی ملے۔ اگر اگلی درخواست پر لوڈ بیلنسر آپ کو کسی دوسرے
سرور پر بھیج دے، تو آپ کے لاگ اِن ایٹریبیوٹس اور دیگر سیشن سے متعلق ایٹریبیوٹس سیریلائز ہو کر نئے سرور پر نقل ہو جاتے ہیں، تاکہ آپ کا JSESSIONID وہاں بھی درست رہے۔
ایک اور صورت جس میں آپ کو اپنے سیشن ایٹریبیوٹس کے لیے سیریلائزیشن کی ضرورت ہے، وہ ہے جب ایپلیکیشن سرور سیشنز کو ڈسک پر منتقل کرنے کا فیصلہ کرے۔ مثلاً Tomcat 8 میں اسے یوں ترتیب دیا جا سکتا ہے کہ یہ تب فعال ہو جب میموری میں رکھنے کے لیے سیشنز بہت زیادہ ہوں، یا جب سرور ری اسٹارٹ ہو اور آپ سیشنز کو برقرار رکھنا چاہیں۔ دیکھیں https://tomcat.apache.org/tomcat-9.0-doc/config/manager.html
ہر Servlet 5.0 سے مطابقت رکھنے والے ایپلیکیشن سرور کو ایسی صورتوں کے لیے بطورِ ڈیفالٹ HTTP سیشنز میں Serializable آبجیکٹس کو سپورٹ کرنا لازم ہے۔
کچھ سرورز میں اسے بدلا یا تبدیل کیا جا سکتا ہے، مثلاً Jackson-JSON سیریلائزیشن یا اس جیسی کسی چیز سے۔ مثال کے طور پر دیکھیں https://stackoverflow.com/questions/46459707/how-to-use-jackson-instead-of-jdkserializationredisserializer-in-spring
ایک اور عام صورت کیشنگ ہے۔ اس کا مقصد آبجیکٹس کو جتنا ممکن ہو تیزی سے دستیاب رکھنا ہے، مثالی طور پر میموری میں، لیکن ممکن ہے عارضی طور پر ڈسک یا کسی بیرونی کیش سرور پر منتقل کیے جائیں۔
اس کے لیے ایک معیاری API (JSR-107) موجود ہے جس نے جان بوجھ کر Serializable آبجیکٹس کو لازمی نہیں بنایا، لیکن زیادہ تر نفاذ اب بھی ڈسک پر منتقلی والے حصے کے لیے بطورِ ڈیفالٹ Java سیریلائزیشن استعمال کرتے ہیں۔
انہیں بھی آپ دوسرے سیریلائزیشن طریقوں کی سپورٹ کے لیے ایڈجسٹ کر سکتے ہیں، مثلاً ehcache کے لیے: https://www.ehcache.org/documentation/3.8/serializers-copiers.html
8. Hibernate: فیچنگ کی حکمتِ عملی
EAGER fetching is a code smell کو مدِنظر رکھتے ہوئے، جسے ایک نہایت کرشماتی ہمہ دان نے لکھا ہے جو Hibernate
کے کنٹریبیوٹر ہیں، FetchType کے طور پر LAZY کو ترجیح دینی چاہیے۔ اگرچہ کچھ ڈیٹا ٹائپس فارن کی کے بغیر بے معنی ہوں، پھر بھی ان ٹائپس کو اکیلے
query کیا ہی جاتا ہے (getCount()، ڈیٹا ٹیبلز وغیرہ)۔
یہاں تمام منظرنامے حل کے اطمینان بخش ہونے کے لحاظ سے ترتیب دیے گئے ہیں، سب سے کم اطمینان بخش سے شروع کرتے ہوئے:
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. سروس ہی میں براہِ راست 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. سروس ہی میں براہِ راست 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 کی تبدیلیوں کو بھانپ لیتا ہے اور آسانی سے ترتیب دیا جا سکتا ہے:
@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;
// ...
}
لوکل ماحول میں سرچ بند ہے:
// 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();
}
فزی سرچ اور سرچ کا رویہ
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 کی ڈیبگنگ
ہمارے Hibernate Search ورژن کو Luke کے ورژن 6.6.0 کے ساتھ ڈیبگ کرنا ضروری ہے۔ یہ صرف اسی ورژن کے ساتھ کام کرتا ہے!
- ڈاؤن لوڈ: https://github.com/DmitryKey/luke/releases
- اس سے
target/فولڈر کے انڈیکسز کھولے جا سکتے ہیں۔ - یہ دیکھنے کا اچھا طریقہ ہے کہ آیا Entities ٹھیک سے انڈیکس ہو بھی رہی ہیں یا نہیں۔
- Luke کے اندر سے سرچ بھی چلائی جا سکتی ہے۔
ElasticSearch
Hibernate Search، ElasticSearch کو بھی سپورٹ کرتا ہے (جو پھر Lucene کی جگہ استعمال ہوتا ہے اور بہتر فزی سرچ / زیادہ Google جیسا احساس دیتا ہے)۔