Żeby to w miarę bezproblemowo działało to flow w aplikacji jest mniej więcej taki:
Wyciągasz dane z bazy, mapujesz do obiektów, które da się wyświetlić (trzymasz w tych obiektach oczywiście jakieś id z bazy żeby było wiadomo co jest do czego), o tych pobranych z bazy zasadniczo możesz zapomnieć. Kończysz transakcję bazodanową, obiekty są w stanie detached, aktualizujesz widok i jest ok.
W razie konieczności zapisania czegoś do bazy:
Otwierasz transakcję, na bazie id z obiektu w widoku wyciągasz odpowiedni obiekt z bazy, robisz co trzeba, zapisujesz, zamykasz transakcję, mapujesz zmienione obiekty z powrotem do takich, które da się wyświetlić, aktualizujesz widok.
I tyle.
Jak zaczniesz się bawić w otwarte zbyt długo transakcje, aktualizowanie encji w przypadkowych momentach, albo co gorsza w cachowanie encji po stronie aplikacji i ich mergowanie przy kolejnej transakcji to popłyniesz.
Wydajnością się nie przejmuj na ten moment. Dojdź w aplikacji do momentu, w którym powyższy flow działa bez problemu, wszystko się zapisuje i masz kontrolę nad tym co się dzieje. Jak zmierzysz, że wydajność jest niezadowalająca to wtedy można myśleć o jakimś cache czy innym sposobie na poprawę działania.
Jeżeli chodzi o pisanie equals i hashCode dla encji to: https://thoughts-on-java.org/ultimate-guide-to-implementing-equals-and-hashcode-with-hibernate/
Nie opieraj logiki aplikacji o encje, jak masz w aplikacji jakiś skomplikowany przepływ danych to wstaw dodatkową warstwę pomiędzy kontroler a repozytorium który ci ogarnie kwestię mapowania, transakcji, etc. Jak flow jest prostyi polega tylko na wyciąganiu z bazy, wyświetlniu i edycji to od biedy nawet w kontrolerach można taką logikę zawrzeć.
Co do pytań - będą to dwa różne obiekty, equals napisany tak jak w linku powyżej da radę. Przy mapowaniu, trzeba zadbać o jakieś połączenie pomiędzy obiektem wyświetlanym a encją, np id encji.