Spring Boot 2.2 User实体重复代码重构方案咨询(类型安全+线程安全)
Hey there! Let's work through this duplicate code problem with a clean, SOLID-compliant approach that's type-safe, thread-safe, and keeps your functionality intact.
First, Let's Diagnose the Root Issue
You're dealing with repeated logic because Job and Genre share identical operations: associating with a User, deduplicating entries, and supporting single/batch additions. Generics are the perfect tool here to abstract this common behavior without sacrificing type safety.
Step 1: Define a Common Interface for Identifiable Entities
First, create a lightweight interface to standardize how we check for duplicates (using the entity's ID as the unique identifier):
public interface Identifiable { Long getId(); }
Then have your Job and Genre entities implement this interface:
@Entity public class Job implements Identifiable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Other fields, getters, setters @Override public Long getId() { return id; } } @Entity public class Genre implements Identifiable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // Other fields, getters, setters @Override public Long getId() { return id; } }
Step 2: Refactor the User Entity with Thread-Safe Collections
Update your User entity to use thread-safe collections for preferences (critical for concurrent access). Choose between:
CopyOnWriteArrayList: If you need to preserve the order of user preferences.ConcurrentSkipListSet: If order doesn't matter (automatically handles deduplication viaequals()/hashCode()).
Here's the User entity setup with ordered, thread-safe lists:
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List<Job> preferredJobs = new CopyOnWriteArrayList<>(); @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List<Genre> preferredGenres = new CopyOnWriteArrayList<>(); // Getters for the collections (avoid exposing setters to maintain encapsulation) public List<Job> getPreferredJobs() { return preferredJobs; } public List<Genre> getPreferredGenres() { return preferredGenres; } }
Step 3: Encapsulate Generic Deduplication Logic
Create a reusable service class to handle the common add/deduplicate logic (follows the Single Responsibility Principle):
@Service public class PreferenceManager { // Add a single element with deduplication (thread-safe atomic operation) public <T extends Identifiable> boolean addUnique(List<T> targetList, T element) { Objects.requireNonNull(element, "Preference element cannot be null"); // Synchronize on the target list to ensure atomic check-and-add synchronized (targetList) { boolean exists = targetList.stream() .anyMatch(item -> Objects.equals(item.getId(), element.getId())); if (!exists) { return targetList.add(element); } return false; } } // Batch add elements with deduplication public <T extends Identifiable> int addUniqueBatch(List<T> targetList, Collection<T> elements) { Objects.requireNonNull(elements, "Preference collection cannot be null"); int addedCount = 0; for (T element : elements) { if (addUnique(targetList, element)) { addedCount++; } } return addedCount; } }
Step 4: Simplify Your Business Logic
Now use the PreferenceManager in your user service to eliminate duplicate code:
@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private PreferenceManager preferenceManager; // Add single Job preference public User addPreferredJob(Long userId, Job job) { User user = getUserOrThrow(userId); preferenceManager.addUnique(user.getPreferredJobs(), job); return userRepository.save(user); } // Batch add Job preferences public User addPreferredJobs(Long userId, List<Job> jobs) { User user = getUserOrThrow(userId); preferenceManager.addUniqueBatch(user.getPreferredJobs(), jobs); return userRepository.save(user); } // Add single Genre preference public User addPreferredGenre(Long userId, Genre genre) { User user = getUserOrThrow(userId); preferenceManager.addUnique(user.getPreferredGenres(), genre); return userRepository.save(user); } // Batch add Genre preferences public User addPreferredGenres(Long userId, List<Genre> genres) { User user = getUserOrThrow(userId); preferenceManager.addUniqueBatch(user.getPreferredGenres(), genres); return userRepository.save(user); } private User getUserOrThrow(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found with ID: " + userId)); } }
Why This Works for Your Requirements
SOLID Compliance:
- Single Responsibility:
PreferenceManagerhandles only deduplication logic;UserServicehandles user-specific operations; entities hold data. - Open/Closed: Add a new preference type (e.g.,
Movie) by just implementingIdentifiable—no changes to existing logic needed. - Liskov Substitution: Any
Identifiableentity works with the generic methods. - Interface Segregation:
Identifiableonly defines the necessarygetId()method. - Dependency Inversion: Logic depends on the abstract
Identifiableinterface, not concrete entities.
- Single Responsibility:
Type Safety: Generics ensure you can't accidentally pass a
Jobto a Genre preference method.Thread Safety:
- Uses thread-safe collections (
CopyOnWriteArrayList). - Synchronizes check-and-add operations to prevent race conditions that could cause duplicates.
- Uses thread-safe collections (
No Functional Breakage: Preserves all existing functionality (single/batch adds, deduplication) while cleaning up code.
Alternative: Use Sets for Automatic Deduplication
If you don't need to preserve the order of preferences, swap List with a thread-safe Set (like ConcurrentSkipListSet). This eliminates manual deduplication logic entirely—just ensure your entities implement equals() and hashCode() based on their ID:
// In Job entity @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Job job = (Job) o; return Objects.equals(id, job.id); } @Override public int hashCode() { return Objects.hash(id); }
Then update the User entity's collections and simplify the PreferenceManager to just use Set.add() (which returns false if the element already exists).
内容的提问来源于stack exchange,提问作者Liquid.Bear

