You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 via equals()/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

  1. SOLID Compliance:

    • Single Responsibility: PreferenceManager handles only deduplication logic; UserService handles user-specific operations; entities hold data.
    • Open/Closed: Add a new preference type (e.g., Movie) by just implementing Identifiable—no changes to existing logic needed.
    • Liskov Substitution: Any Identifiable entity works with the generic methods.
    • Interface Segregation: Identifiable only defines the necessary getId() method.
    • Dependency Inversion: Logic depends on the abstract Identifiable interface, not concrete entities.
  2. Type Safety: Generics ensure you can't accidentally pass a Job to a Genre preference method.

  3. Thread Safety:

    • Uses thread-safe collections (CopyOnWriteArrayList).
    • Synchronizes check-and-add operations to prevent race conditions that could cause duplicates.
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 23:07:57