Giriş
Ön yüz ile arka yüzün ayrıldığı bu çağda hayatımız sayısız fırsat ve zorlukla dolu——birden ortaya çıkıp insanı çileden çıkaran hatalar da buna dâhil. Bugün konuşacağımız konu, sayısız geliştiriciyi gülse mi ağlasa mı bilemez hâle getiren klasik bir sorun:Arka uç Long Tür ID'sinin fazla uzun olması ön yüzde sayısal hassasiyet kaybına yol açıyor。Bu konuya gelince, gerçekten de “bütün kötülüklerin kaynağı” denebilir; sonuçta JavaScript yalnızca güvenli biçimde işleyebilir: Number.MAX_SAFE_INTEGER(yani 9007199254740991)sınırı içindeki sayıları mı?
Bu sorun yüzünden uzun uzun kafa yorduysanız endişelenmeyin, yalnız değilsiniz. Bugün bu “yüzyılın sorununu” yalnızca mizahi bir dille incelemekle kalmayacak, onu kökünden çözmenizi de sağlayacağız; böylece kodunuz artık “hassasiyetini kaybetmeyecek”.
Sorunun arka planı:Neden Long Bu kadar uzun mu?
Önce şuradan başlamalıyız Long türünden başlayalım.Long,yani Java içindeki 64 bitlik tamsayı türü, işlemeyi sevenler için Büyük veri、büyük sayılarla çalışan Java için bu tür âdeta bir nimettir. Ancak ön uç dünyası biraz “dar görüşlüdür”; yalnızca 53 bit sınırı içindeki tam sayıları işlemeyi bilir. Evet, yanlış duymadınız, bu konuda JavaScript Sanki “biraz geç kavrayan” yaşlı bir âlim gibi, daha büyük sayılarla karşılaşınca ondalık noktası ve üslerle oynamaya başlıyor, sonunda da sizi umutsuzluğa düşüren bir sayı çıkarıyor. Böylece hassasiyet kaybı sorunu bir hayalet gibi projenizde dolaşmaya başlıyor.
İlk karşılaşma: Hassasiyet kaybına dair meseleler
Hikâye, bir gün gelen hata bildirimiyle başlıyor:“Geliştirici abi, baksana bu ID neden değişmiş?Bu benim Veritabanı içindeki o ID işte!” Kaşlarını çatarak bakıyorsun; evet,9223372036854775807 şuna dönüştü: 9223372036854776000,ah, fazladan çıkan bu rakamlar adeta sihir gibi. Oysa arka ucun verdiği değer doğru!Başını kaşırken içinden şöyle geçiriyorsun:“Bu kesin ön yüzün hatası!”
Sorumluluk ön yüzde mi, arka uçta mı?——Kökene inmek
Aslında bu sorunun suçunu ön yüze atmak da tamamen temelsiz değil. Gelin şuna bakalım: JavaScript sayıları işlerken gösterdiği “zayıf nokta”.JavaScript ait Number Tür, IEEE 754 standardına dayanan çift duyarlıklı kayan nokta biçimindedir ve yalnızca 53 ikili basamağı güvenli biçimde temsil edebilir, yani Number.MAX_SAFE_INTEGER değeri——9007199254740991。
Başka bir deyişle, bu aralığın dışındaki tam sayılar için,JavaScript “hassasiyetten ödün vermeye” başlar; “beyin kapasitesi” birden yetersiz kalır. Böylece, sizin Long türündeki kimlikler, onun gözünde pek de “seksi” olmayan bir sayı yığınına dönüşür. Tam da bu yüzden ön uç, bu akıl almaz uzunluktaki kimlikleri işlerken küçük bir “fedakârlık” yapmak zorunda kalır; sonuçta başta aslına sadık olan kimliğiniz kırpılır ve “ilginç” yeni bir sayıya dönüşür.
Şaşkınlıktan soğukkanlılığa:Çözüm arayışı
Sorunun kaynağı bulunduğuna göre, artık biz geliştiricilerin hünerlerini gösterme sırası geldi. Şimdi sizi birkaç çözümü ayrıntılı biçimde incelemeye götüreceğim ve her birinin artılarını ve eksilerini anlatacağım; sonuçta bütün yollar Roma'ya çıkar.
1. Doğrudan metin dizisine dönüştürme:kaba ama etkili
Böyle bir sorunla karşılaşınca aklımıza ilk gelen kuşkusuz en basit ve kaba yöntemdir: doğrudan Long türündeki Veri dönüştürme dizeye çevirsek yeterli olmaz mı! Madem JavaScript kayan noktalı sayıların körü körüne hayranıysa, biz de sorunu doğrudan ona geri atalım ve şöyle diyelim: “Bunu yalnızca bir dize olarak ele alman yeterli, uzunluğu konusunda endişelenme!”
Bunu nasıl yapabiliriz?Aslında çok basit, şunu kullanarak Jackson tarafından sağlanan ToStringSerializer,kolayca Long Dizeye dönüştürün.
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
public class UserDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
// 其他字段
}Böylece, arka uç döndürdüğünde UserDto olduğunda,Long türündeki id Bir dize olarak serileştirilir ve ön uç da bunu dize olarak alır; böylece hassasiyet sorunu kolayca çözülür. JavaScript'in alnına bir not yapıştırmak gibi: “Bu bir dize, senin uğraşmana gerek yok!”
2. Ön yüzde kullanım BigInt:büyük sayılarla da kesin hesaplama yapılmasını sağlamak
Elbette basit ve doğrudan yöntemler her senaryoya uygun değildir. Ön yüzün bu ID üzerinde bir matematiksel işlem yapması gerektiğini düşünün; onu doğrudan bir metin dizgesine dönüştürmek pek iyi olmaz. Peki ön yüz bu “aşırı uzun” ID'leri nasıl işlemeli?
Neyse ki,JavaScript Hiçbir yararı yok da değil. Şunu getirdi BigInt Daha sonra,JavaScript Sonunda yalnızca ondalık noktalarla oynayabilen o budala olmaktan çıktı.BigInt keyfî hassasiyetteki tam sayıları işlemek için özel olarak tasarlanmış yeni bir ilkel veri türüdür. Şöyle yapabilirsiniz:
const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808nBöylece ön uçta hassas biçimde işleyebilirsiniz Long türündeki veriler, hassasiyet kaybı sorununu önler. Elbette burada küçük bir hatırlatma var:BigInt Tüm tarayıcılar tarafından desteklenmez, dolayısıyla uygulamanızın çalıştığı ortamın uyumlu olduğundan emin olmanız gerekir.
3. Özel serileştirme:karmaşık sorunları basitleştirmek
Yukarıdaki yöntemler çoğu sorunu çözebilse de bazen daha ince ayrıntılar üzerinde kontrol gerektiren durumlarla karşılaşırız. Böyle zamanlarda,Jackson için özel serileştirici işte burada devreye girer.
İhtiyaçlara göre esnek biçimde kontrol etmek için özel bir serileştirici yazabiliriz Long türündeki alanların serileştirilme süreci. Örneğin, serileştirme sırasında şunun yapılıp yapılmayacağına karar verebiliriz: Long Dizeye dönüştürmek,ya da belirli koşullara göre farklı işlemler yapmak:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class CustomLongSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
gen.writeString(value.toString());
}
}
}Ardından bu özel serileştiriciyi gereken yerde uygulayın:
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class UserDto {
@JsonSerialize(using = CustomLongSerializer.class)
private Long id;
// 其他字段
}Böylece tüm serileştirme sürecini kontrol edebilir, her birinin Long istediğiniz şekilde işlenebilir。Elbette bu yöntem esnek olsa da biraz daha karmaşıktır; birkaç satır daha kod yazmayı gerektirir ve bakım maliyetini de artırabilir。
4. Genel düzeyde işleme: Zaman ve emek tasarrufu sağlayan çözüm
Bu sorunu genel düzeyde çözmek ve her alan için anotasyon yapılandırma zahmetinden kurtulmak istiyorsanız, genel yapılandırmayı değerlendirebilirsiniz ObjectMapper。Şurada Spring Boot içinde genel yapılandırmayı ayarlama ObjectMapper,tümünün şu işlemi yapmasını sağlayabilirsiniz: Long Tür alanlarının tümü otomatik olarak dizgeye dönüştürülür.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
mapper.registerModule(module);
return mapper;
}
}Sorunu bir defada kalıcı olarak çözer; herhangi bir alan için serileştirici yapılandırmayı unutma kaygısı yaşamazsınız ve tüm proje hassasiyet kaybı olmamasının rahatlığından yararlanır. Elbette genel yapılandırmayı kullanırken dikkatli olmak gerekir; dönüştürülmesini istemediğiniz bazı Long alanı, bu nedenle dikkatlice değerlendirmek gerekir.
Gerçek bir örnek:Bir Bug'ı çözmenin dolambaçlı yolu
Bu çözümlerin gerçek etkilerini daha iyi anlayabilmeniz için gerçek bir projeden bir hikâye paylaşayım. Bu proje kullanıcı ID'lerinin yönetimini içeriyordu; kullanıcı sayısı çok büyük olduğundan arka uçta kullanılan Long türündeki benzersiz tanımlayıcı. Proje yayına alındıktan birkaç gün sonra kullanıcı geri bildirimleri gelmeye başladı:“Benim ID'm neden değişmiş?”
Birinci adım: Sorunun kökenini araştırma
Önce ön yüz kodunu inceledik ve şunu bulduk: JavaScript ait Number türü, 19 basamak uzunluğundaki bu sayıyı gerçekten de tam olarak temsil edemediği için hassasiyet kaybı oluştu. Ardından arka uç kodunu inceledik ve her ne kadar Long türündeki veriler arka uçta doğrudur, ancak REST üzerinden API Ön yüze döndürülürken sayının hassasiyeti kayboldu.
İkinci adım: Uygun çözümü seçme
Sorunu hızla çözmek için önce şunu kullanmaya karar verdik: @JsonSerialize(using = ToStringSerializer.class) bu basit ve etkili yöntem. Bu yöntem sayesinde ön ucun hassasiyet kaybına uğramış sayılar almasını başarıyla önledik。
Ancak sorun tamamen çözülmemişti. Daha sonraki gereksinimlerde ön yüzün ID'ler üzerinde bazı işlemler yapması, örneğin kullanıcı ID'lerini sıralaması gerekiyordu. Bu noktada dizeler biraz yetersiz kalıyordu.
Üçüncü adım:Kullanım BigInt Ön yüzdeki hesaplama sorununu çözme
Bunun üzerine ön uçta kullanmaya karar verdik BigInt。Şunu kullanarak BigInt,ön uç bu aşırı uzun ID’leri yalnızca tam doğrulukla saklamakla kalmaz, gerekli matematiksel işlemleri de yapabilir. Testler, bu yöntemin başlıca tarayıcıların tümünde iyi çalıştığını gösterdi; tek dezavantajı, bazı eski tarayıcı sürümlerini pek iyi desteklememesidir.
Dördüncü adım:Son genel yapılandırma
Gelecekte benzer sorunların tekrar yaşanmasını önlemek için,şunu yapmaya karar verdik: ToStringSerializer Genel olarak geçerli olacak şekilde yapılandırın. Böylece tüm Long türündeki veriler serileştirme sırasında otomatik olarak dizelere dönüştürülür; böylece hem ön uçtaki verilerin doğruluğu korunur hem de kodda yinelenen yapılandırmalar azalır.
Son söz:Hassasiyet kaybına son veren
Bu örnekten görülebileceği gibi,her ne kadar Long Türün duyarlılık kaybı sorunu basit görünse de gerçek projelerde pek çok gizli soruna yol açabilir. Çeşitli çözümleri karşılaştırıp deneyerek sonunda projemize en uygun çözümü bulduk.
Umarım bu esprili ve ayrıntılı blog yazısı, şu konuyla uğraşırken sana yardımcı olur Long tür hassasiyeti kaybı sorunlarıyla karşılaştığınızda daha az dolambaçlı yoldan ilerlemenizi sağlar。Benzer deneyimleriniz varsa yorumlarda hikâyenizi paylaşabilirsiniz; belki deneyiminiz diğer geliştiricilere daha fazla ilham verir。
Son olarak unutmayın, kod da hayat gibidir; ara sıra yaşanan “kayıplar” korkutucu değildir, önemli olan onu yeniden rayına oturtacak uygun “serileştiriciyi” bulmaktır. Hepinizin kodunun artık “hassasiyet kaybı” yaşamamasını, kesinliğini koruyarak sorunsuz ilerlemesini dilerim!