【Postopia Dev Log】Week 3

Week3

写评论查询的时候遇到了递归查询和递归结构构建的问题,这样的问题是表层学习而不真正深入使用知识、框架时难以遇到并学习如何解决的。

enum in @Query

@Query("select new com.heslin.postopia.dto.post.PostSummary(p.subject, p.positive_count, p.negative_count, p.comment_count, u.username, u.nickname, u.avatar) from Post p JOIN p.user u where p.space_id = :id and p.status != com.heslin.postopia.enums.PostStatus.DRAFT")

record

Generics

Records in Java can be combined with generics. Records, introduced in Java 14 as a preview feature and made stable in Java 16, provide a concise way to model immutable data. Generics allow types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. Combining these two can lead to more flexible and reusable code.

Here is an example of how you can define and use a generic record in Java:

// Define a generic record
public record Pair<K, V>(K key, V value) {}

// Usage example
public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("age", 30);
        System.out.println("Key: " + pair.key());
        System.out.println("Value: " + pair.value());
    }
}

In this example:

  1. Pair<K, V> is a generic record with two type parameters, K and V.
  2. The Pair record has two components, key and value, of types K and V respectively.
  3. When creating an instance of Pair, you specify the actual types to use, such as String and Integer in this case.
  4. You can then use the key() and value() methods to access the components of the record.

This example illustrates how generics can be seamlessly integrated with records to create versatile and type-safe data structures.

Extension

No, records in Java cannot be extended. Records are implicitly final, meaning they cannot be subclassed. This design choice aligns with the primary purpose of records, which is to provide a simple and concise way to create immutable data carriers. Allowing inheritance would complicate this model and could undermine the immutability guarantees that records provide.

Here’s a brief overview of some key characteristics of records:

  1. Immutability: Records are designed to be immutable. All fields in a record are implicitly private and final.
  2. Compact Syntax: Records provide a compact syntax for declaring classes that are primarily used to hold data.
  3. Final and Non-Extendable: Records are implicitly final, meaning you cannot extend a record class.

If you need to share common functionality or data among multiple types, consider using composition or interfaces instead of inheritance. Here is an example using composition:

// Common functionality encapsulated in a class
class CommonData {
    private final String commonField;

    public CommonData(String commonField) {
        this.commonField = commonField;
    }

    public String getCommonField() {
        return commonField;
    }
}

// Record using composition
public record SpecializedRecord(String specificField, CommonData commonData) {}

public class Main {
    public static void main(String[] args) {
        CommonData commonData = new CommonData("Shared Data");
        SpecializedRecord record = new SpecializedRecord("Specific Data", commonData);

        System.out.println("Specific Field: " + record.specificField());
        System.out.println("Common Field: " + record.commonData().getCommonField());
    }
}

In this example, the CommonData class encapsulates the shared functionality, and the SpecializedRecord record uses composition to include this common data. This approach maintains the benefits of immutability and concise syntax while avoiding the pitfalls of inheritance.

Break circular dependencies

In Spring Boot, it’s generally not advisable to have two services autowire each other as fields because it can create a circular dependency. Circular dependencies can make your application context fail to start and are generally considered a bad design practice.

However, if you still need to achieve this, there are some workaround strategies. Here are a few approaches:

1. Setter Injection

This method uses setter methods instead of field injection to avoid circular dependencies.

@Service
public class ServiceA {

    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void methodA() {
        serviceB.methodB();
    }
}

@Service
public class ServiceB {

    private ServiceA serviceA;

    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void methodB() {
        serviceA.methodA();
    }
}

2. @Lazy Annotation

You can use the @Lazy annotation to delay the initialization of one of the services, breaking the circular dependency.

@Service
public class ServiceA {

    private final ServiceB serviceB;

    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void methodA() {
        serviceB.methodB();
    }
}

@Service
public class ServiceB {

    private final ServiceA serviceA;

    @Autowired
    public ServiceB(@Lazy ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void methodB() {
        serviceA.methodA();
    }
}

3. Redesign Your Services

Often, circular dependencies are a sign that your service design needs to be refactored. Consider whether you can split responsibilities differently to avoid the circular dependency. For example, you could introduce a third service or a common interface.

@Service
public class ServiceA {

    private final CommonService commonService;

    @Autowired
    public ServiceA(CommonService commonService) {
        this.commonService = commonService;
    }

    public void methodA() {
        commonService.commonMethod();
    }
}

@Service
public class ServiceB {

    private final CommonService commonService;

    @Autowired
    public ServiceB(CommonService commonService) {
        this.commonService = commonService;
    }

    public void methodB() {
        commonService.commonMethod();
    }
}

@Service
public class CommonService {

    public void commonMethod() {
        // Common logic here
    }
}

4. Application Context

You can also manually fetch the beans from the application context, but this is generally not recommended and should be used as a last resort.

@Service
public class ServiceA implements ApplicationContextAware {

    private ServiceB serviceB;
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        this.serviceB = applicationContext.getBean(ServiceB.class);
    }

    public void methodA() {
        serviceB.methodB();
    }
}

@Service
public class ServiceB {

    private final ServiceA serviceA;

    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void methodB() {
        serviceA.methodA();
    }
}

Conclusion

While you can technically create circular dependencies using these methods, it’s usually better to refactor your code to avoid such designs. Circular dependencies can make your code harder to understand, test, and maintain. Consider whether there are better ways to structure your services to keep them decoupled.

Recursive Model Query

Check post with the same name for details

Licensed under CC BY-NC-SA 4.0
Last updated on Aug 16, 2024 00:00 UTC
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy