IDEA

Postfix Completion

SHOW CONTENTS

Postfix completion allows writing code faster by adding expressions after a variable or value. It can be customized to suit specific coding needs.

"Hello".var  String s = "Hello";


Live Templates

SHOW CONTENTS

Live Templates are predefined code snippets that can be inserted quickly using a shortcut. It can be customized to suit specific coding needs.

sout  System.out.println();


Keyboard Shortcuts

SHOW CONTENTS
  • Show Hint: Alt + Enter
  • Show Parameter Info: Ctrl + P
  • Surround with Selected Code Block: Ctrl + Alt + J
  • Start a New Line: Shift + Enter
  • Start a New Line Before Current: Ctrl + Alt + Enter
  • Show Live Template: Ctrl + J
  • Rename in Batch: Shift + F6
  • Extract Method: Ctrl + Alt + M
  • Override Methods: Ctrl + O
  • Implement Methods: Ctrl + I
  • Optimize Imports: Ctrl + Alt + O
  • View Source Code: Ctrl + NCtrl + F12
  • Go to Previous Edit Location: Ctrl + Alt + ←
  • Go to Next Edit Location: Ctrl + Alt + →
  • Show Inheritance Tree: Ctrl + H
  • Show Quick Documentation: Ctrl + Q
  • Show UML Diagram: Ctrl + Alt + U
  • Go to Specified Line: Ctrl + G → Enter line number
  • Go to Variable Definition: Ctrl + Alt + B
  • Expand Code Block: Ctrl + Shift + +
  • Collapse Code Block: Ctrl + Shift + -


Debug

Method Breakpoints

SHOW CONTENTS

Method breakpoints are a type of debugger breakpoint that pauses program execution when a specific method or function is entered or exited. Below is an example of debugging resize() method.

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("1", 1);
    }
}

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // ...
}            

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // ...
}


Field Breakpoints

SHOW CONTENTS

Field breakpoints are a special type of breakpoint that trigger when a specific field is accessed or modified. Below is a real world example:

public class Main {
    public static void main(String[] args) {
        Person person = new Person(1);
        System.out.println("person id:" + person.getId());
        person.setId(2);
        System.out.println("person = " + person);
    }
}

class Person {
👁private int id;

    public Person(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                '}';
    }
}


Conditional Breakpoints

SHOW CONTENTS

Conditional breakpoints pause execution only when a specified condition is true. Below is a sample example:

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i < 11; i++) {
            if (i % 2 == 0) {
                System.out.println("i = " + i);
            }
        }
    }
}

// Condition: i % 2 == 0


Exceptional Breakpoints

SHOW CONTENTS

Exception breakpoints pause execution when specified exceptions are thrown, even if they’re caught and handled. Below is a sample example:

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getName().toLowerCase());
    }
}

// 1. Click `View Breakpoints` 
// 2. Select ➕ icon
// 3. Add `Java Exception Breakpoints`
// 4. Search `NullPointerException`

class Person {
    String name;

    public String getName() {
        return name;
    }

    public Person setName(String name) {
        this.name = name;
        return this;
    }
}


Thread Debug

SHOW CONTENTS

Thread breakpoints are used in multi-threaded applications that allow pausing execution only when a specific thread hits a breakpoint. Below is a sample example:

public class Main {
    public static void main(String[] args) {
        Runnable task = () -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " started");
            processData(threadName);  // Set thread-specific breakpoint here
        };

        Thread worker1 = new Thread(task, "Worker-1");
        Thread worker2 = new Thread(task, "Worker-2");

        worker1.start();
        worker2.start();
    }

    private static void processData(String threadName) {
        if (threadName.equals("Worker-1")) {
            System.out.println("Special logic for Worker-1");
        }
    }
}

// 1. Click breakpoints in the gutter
// 2. Choose `Thread` option instead of `All`
// 3. Add condition: "Worker-1".equals(Thread.currentThread().getName())


Custom Data Views

SHOW CONTENTS

Custom data views allow controlling how objects are displayed in the debugger, making complex objects easier to inspect.

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();

🔴        map.put(1, "Signal");
        map.put(2, "こういち");
    }
}

// 1. Select `map` instance and right click
// 2. Choose `Custom Data Views...`
// 3. Unselect `Hide null elements in arrays and collections` option
// 4. Unselect `Enable alternative view for Collection classes`


New Projects

SHOW CONTENTS
  1. Config Tomcat: https://youtu.be/spto4kcMHUI?si=_vsBnV3JElTcvhCZ
  2. Create Maven Java Project: https://youtu.be/rUZZTCTpHCQ?si=isejuUB7irsGIU3Z
  3. Create Maven Java Web: https://youtu.be/h6Ld1N9ZzX8?si=BckuYaAd0qDl_QnR


Maven

Introduction to Maven

SHOW CONTENTS

Maven is a build automation and project management tool primarily used for Java projects. It follows the concept of Project Object Model (POM), which is defined in an XML file (pom.xml) at the root of the project.


settings.xml Configuration

SHOW CONTENTS
./
├── bin/
│   ├── m2.conf
│   ├── mvn*
│   ├── mvn.cmd
│   ├── mvnDebug*
│   ├── mvnDebug.cmd
│   └── mvnyjp*
├── boot/
│   ├── plexus-classworlds-2.7.0.jar
│   └── plexus-classworlds.license
├── conf/
│   ├── logging/
│   │   └── simplelogger.properties
│   ├── settings.xml  # CUSTOMIZE DEFAULT BEHAVIOR IN THIS FILE
│   └── toolchains.xml
├── ...

The settings.xml file in Maven is typically used to customize the default behavior according to user needs. Common modifications include configuring repository locations, setting up mirrors, and defining profiles.

  1. Configuring Repository Location:
<!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository
  <localRepository>/path/to/local/repo</localRepository>
  -->

<localRepository>/Users/signalfish/maven-repository</localRepository>
  1. Setting Up Mirrors:
<mirrors>
    <!-- mirror
        | Specifies a repository mirror site to use instead of a given repository. The repository that
        | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
        | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
        |
    <mirror>
        <id>mirrorId</id>
        <mirrorOf>repositoryId</mirrorOf>
        <name>Human Readable Name for this Mirror.</name>
        <url>http://my.repository.com/repo/path</url>
    </mirror>
        -->

    <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>
</mirrors>
  1. Defining Profiles:
<profiles>
    <profile>
      <id>env-dev</id>

      <activation>
        <property>
          <name>target-env</name>
          <value>dev</value>
        </property>
      </activation>

      <properties>
        <tomcatPath>/path/to/tomcat/instance</tomcatPath>
      </properties>
    </profile>

    <profile>
        <id>jdk-1.8</id>
        <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
        </activation>
        <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        </properties>
    </profile>

    <profile>
        <id>jdk-17</id>
        <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>17</jdk>
        </activation>
        <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
        </properties>
    </profile>
</profiles>


GAVP

SHOW CONTENTS

In Maven, GAVP refers to four key components that are used to uniquely identify and describe an artifact (e.g., WAR, JAR, or POM) in a Maven repository.

  • G stands for GroupID, which represents the organization or group responsible for the artifact. It typically follows the reverse domain name convention, such as <groupId>com.example</groupId> or <groupId>dev.signalyu</groupId>.
  • A stands for ArtifactID, which identifies the specific artifact within the group. It usually matches the project name or module name, such as <artifactId>my-app</artifactId>.
  • V stands for Version, which specifies the version of the artifact, such as <version>1.0.0</version>.
  • P stands for Packaging, which specifies the type of artifact. It tells Maven what kind of output to expect from the project, such as a JAR file for a library or a WAR file for a web application, such as <packaging>jar</packaging>.


Common Maven Commands

SHOW CONTENTS
  • mvn compile: Compiles the source code of the project.
  • mvn test: Compiles the source code, runs unit tests, and reports the results.
  • mvn package: Compiles the source code, runs unit tests, and packages the compiled code into desired artifact.
  • mvn clean: Removes the previously compiled files and generated artifacts.
  • mvn install: Compiles the source code, runs unit tests, packages the compiled code into an artifact, and installs it into local Maven repository.
  • mvn deploy: Compiles the source code, runs unit tests, packages the compiled code into an artifact, and deploys the artifact to a remote repository.


Maven Dependency Download Failure

SHOW CONTENTS

When Maven fails to download dependencies, follow these steps to resolve the issue:

  1. Check the network connection: Ensure a stable internet connection. If an internal network is used, verify the mirror configuration in settings.xml to ensure it’s correctly set up.
  2. Verify the dependency version: Double-check the version of the dependency specified in pom.xml.
  3. Clean the local repository: If the previous steps don’t resolve the issue, try cleaning the local Maven repository or deleting the specific dependency directory in the repository.
  4. Invalidate caches in the IDE: In IntelliJ IDEA, navigate to File -> Invalidate Caches to clear any cached information that might be causing issues. Then, restart the IntelliJ IDEA.


Dependency Inheritence

SHOW CONTENTS

In Maven, dependency inheritance refers to the ability to inherit dependencies from a parent project. This feature enables better management of dependencies in multi-module projects, where a child module can inherit dependency configurations from the parent POM.

  1. Parent POM file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>parent-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>child-project1</module>
        <module>child-project2</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.3.12</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <!-- These dependencies can be inherited by child projects -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
    </dependencies>
</project>
  1. Child POM file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>parent-project</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>  <!-- Path to the parent POM -->
    </parent>

    <artifactId>child-project1</artifactId>

    <dependencies>
        <!-- Child module can use the dependency inherited from the parent -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
    </dependencies>
</project>

In the above code, The spring-core dependency is inherited from the parent, and the version is automatically resolved by Maven from the parent’s dependencyManagement section. The commons-lang3 dependency from the parent is also inherited, but does not need to be re-declared in the child POM.

The difference between dependencyManagement and dependencies is that dependencies directly add dependencies to the project build, meaning they are included in the project’s final artifact. On the other hand, dependencyManagement specifies the version and configuration of dependencies for inheritance in child modules, without directly adding them to the project build.


Git

Backend Development

Microservices Architecture

Class Design

Features

Read

SHOW CONTENTS

Base Attribute ER Diagram

  1. Sample Code:
@RestController
@RequestMapping(value = "/api/album")
public class BaseCategoryApiController {

    @Autowired
    BaseCategoryService baseCategoryService;

    @GetMapping("/category/findAttribute/{category1Id}")
    public Result<List<BaseAttribute>> getAttributeByCategory1Id(@PathVariable @Min(1) Long category1Id) {
        List<BaseAttribute> list = baseCategoryService.getAttributeByCategory1Id(category1Id);
        return Result.ok(list);
    }
}

public interface BaseCategoryService extends IService<BaseCategory1> {

    List<BaseAttribute> getAttributeByCategory1Id(Long category1Id);
}

public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, BaseCategory1> implements BaseCategoryService {
    @Autowired
    private BaseAttributeMapper baseAttributeMapper;

    public List<BaseAttribute> getAttributeByCategory1Id(Long category1Id) {
        return baseAttributeMapper.getAttributeByCategory1Id(category1Id);
    }
}

@Mapper
public interface BaseAttributeMapper extends BaseMapper<BaseAttribute> {

    List<BaseAttribute> getAttributeByCategory1Id(@Param("category1Id") Long category1Id);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="dev.signalyu.tingshu.album.mapper.BaseAttributeMapper">
    <resultMap id="baseAttributeMap" type="dev.signalyu.tingshu.model.album.BaseAttribute" autoMapping="true">
        <id column="id" property="id" />
        <collection property="attributeValueList" ofType="dev.signalyu.tingshu.model.album.BaseAttributeValue" autoMapping="true">
            <id column="base_attribute_value_id" property="id" />
        </collection>
    </resultMap>

    <select id="getAttributeByCategory1Id" resultMap="baseAttributeMap">
        select ba.id,
               ba.category1_id,
               ba.attribute_name,
               bav.id as base_attribute_value_id,
               bav.value_name
        from base_attribute as ba
                 left join base_attribute_value bav
                           on ba.id = bav.attribute_id
        where category1_id = #{category1Id}
          and ba.is_deleted = 0;
    </select>
</mapper>

@Data
@TableName("base_attribute")
public class BaseAttribute extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @Schema(description = "Category 1 ID")
    @TableField("category1_id")
    private Long category1Id;

    @Schema(description = "Attribute display name")
    @TableField("attribute_name")
    private String attributeName;

    @TableField(exist = false)
    private List<BaseAttributeValue> attributeValueList;
}

@Data
@TableName("base_attribute_value")
public class BaseAttributeValue extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @Schema(description = "Attribute ID")
    @TableField("attribute_id")
    private Long attributeId;

    @Schema(description = "Attribute value name")
    @TableField("value_name")
    private String valueName;
}
  1. Result Set:
id category1_id attribute_name base_attribute_value_id value_name
1 2 Audiobook Type 1 Male-oriented Novels
1 2 Audiobook Type 2 Female-oriented Novels
2 2 Payment Type 3 Paid
2 2 Payment Type 4 Free
  • Architecture Flow:
Controller → BaseCategoryService (Interface) 
           → BaseCategoryServiceImpl (Implementation)
               → Uses:
                  - baseMapper (for BaseCategory1 CRUD)
                  - baseAttributeMapper (for custom queries)
  • namespace: The namespace defines the Java interface that the XML mapper is associated with. It is used to uniquely identify the mapper in the context of MyBatis. In the above code, dev.signalyu.tingshu.album.mapper.BaseAttributeMapper represents the fully qualified name of the Java interface that the mapper belongs to. This means that the methods declared in the interface BaseAttributeMapper will use the SQL statements defined in the XML file.
  • resultMap/collection/autoMapping: The resultMap is used to define how the result set from a query is mapped to Java objects. The collection element is used to map a collection of objects (e.g., List, Set) that are related to the main object being mapped. The autoMapping attribute tells MyBatis to automatically map the fields of the result set to the properties of the Java object. In the above code, The id="baseAttributeMap" defines a mapping for BaseAttribute class, where the columns of the result set are mapped to the fields of the BaseAttribute class.
    • The column="id" in result set maps to the property="id" field in BaseAttribute.
    • The column="base_attribute_value_id" inside the collection tag maps to the property="id" field in BaseAttributeValue. Mybatis - Mapping Between of Result Set and Corresbonding Property of Java Object
  • The select statement inside the mapper doesn’t query attribute_id field. When mybatis performs mapping, the attributeId in BaseAttributeValue objects will be null.


Update

SHOW CONTENTS

ER Diagram: album_info &amp; album_stat

  1. Sample Code:
@RestController
@RequestMapping("api/album")
public class AlbumInfoApiController {

    @Autowired
    private AlbumInfoService albumInfoService;

    @PostMapping("/albumInfo/saveAlbumInfo")
    public Result saveAlbumInfo(@RequestBody @Validated AlbumInfoVo albumInfoVo){
        Long userId = AuthContextHolder.getUserId();
        albumInfoService.saveAlbumInfo(userId, albumInfoVo);
        return Result.ok();
    }
}

public interface AlbumInfoService extends IService<AlbumInfo> {

    void saveAlbumInfo(Long userId, AlbumInfoVo albumInfoVo);
    
    void saveAlbumStat(Long albumId, String statType, int statNum);
}

@Service
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Override
    @Transactional(rollbackFor = Exception.class) 
    public void saveAlbumInfo(Long userId, AlbumInfoVo albumInfoVo) {
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);

        albumInfo.setUserId(userId);
        albumInfo.setIncludeTrackCount(0);
        albumInfo.setIsFinished("0");
        albumInfo.setTracksForFree(5);
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);

        albumInfoMapper.insert(albumInfo);
        Long albumId = albumInfo.getId();

        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                albumAttributeValue.setAlbumId(albumId);
                albumAttributeValueMapper.insert(albumAttributeValue);
            }

        }
      
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_BUY, 0);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT, 0);
    }

    @Override
    public void saveAlbumStat(Long albumId, String statType, int statNum) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        albumStat.setStatNum(statNum);
        albumStatMapper.insert(albumStat);
    }
}

The BaseMapper<T> interface provides ready-to-use database operations, eliminating the need for a mapper.xml file. As a result, the three mappers—albumInfoMapper, albumAttributeValueMapper, and albumStatMapper—can utilize the insert method without the need to write a mapper.xml file.

public interface AlbumInfoService extends IService<AlbumInfo> {}

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {}

public interface BaseMapper<T> extends Mapper<T> {}

public interface AlbumInfoMapper extends BaseMapper<AlbumInfo> {}
public interface AlbumAttributeValueMapper extends BaseMapper<AlbumAttributeValue> {}
public interface AlbumStatMapper extends BaseMapper<AlbumStat> {}

public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {
    // ...
    albumInfoMapper.insert(albumInfo);
    // ...
    albumAttributeValueMapper.insert(albumAttributeValue);
    // ...
    albumStatMapper.insert(albumStat);
}


Delete


Using Views

SHOW CONTENTS

Views are useful in several scenarios, including when complex queries are frequently used or when providing controlled access to specific data for security purposes.

Category ER Diagram

  1. Sample Code:
CREATE OR REPLACE VIEW base_category_view AS
SELECT
    bc3.id,
    bc1.id category1_id,
    bc1.name category1_name,
    bc2.id category2_id,
    bc2.name category2_name,
    bc3.id category3_id,
    bc3.name category3_name,
    bc3.is_deleted,
    bc3.create_time,
    bc3.update_time
FROM base_category1 bc1  
LEFT JOIN base_category2 bc2 ON bc2.category1_id = bc1.id 
LEFT JOIN base_category3 bc3 ON bc3.category2_id = bc2.id;

-- Basic query
SELECT * FROM base_category_view;

-- Filtered query
SELECT category1_name, category2_name, category3_name 
FROM base_category_view 
WHERE is_deleted = 0;


Grouping

SHOW CONTENTS
  1. Sample Data:
category1_id category1_name category2_id category2_name category3_id category3_name
1 Music 101 Music Effects 1001 Hypnotic Music
1 Music 101 Music Effects 1002 Relaxation Music
1 Music 101 Music Effects 1003 Energizing Music
1 Music 101 Music Effects 1004 Prenatal Music
1 Music 101 Music Effects 1005 Workout Music
1 Music 101 Music Effects 1006 Leisure Music
1 Music 102 Course Guidance 1007 Sleep Assistance Guide
1 Music 102 Course Guidance 1008 Relaxation Guide
1 Music 102 Course Guidance 1009 Focus Guide
1 Music 102 Course Guidance 1010 Children Sleep Guide
1 Music 102 Course Guidance 1011 Other
1 Music 103 Host Music Show 1012 Classic Music Recommendation
1 Music 103 Host Music Show 1013 Hot Song Roundup
1 Music 103 Host Music Show 1014 Cover Songs
1 Music 103 Host Music Show 1015 Music Education
1 Music 103 Host Music Show 1016 Music Stories
2 Audiobook 104 Male Frequency Novels 1018 Military Novels
2 Audiobook 104 Male Frequency Novels 1019 Sports Novels
2 Audiobook 104 Male Frequency Novels 1020 Alternate Dimensions
2 Audiobook 104 Male Frequency Novels 1021 Fantasy
2 Audiobook 104 Male Frequency Novels 1022 Suspense & Paranormal
2 Audiobook 104 Male Frequency Novels 1023 Martial Arts
2 Audiobook 104 Male Frequency Novels 1024 Urban
2 Audiobook 104 Male Frequency Novels 1025 Immortal Heroes
2 Audiobook 104 Male Frequency Novels 1026 Fantasy Fiction
2 Audiobook 104 Male Frequency Novels 1027 Historical Novels
2 Audiobook 104 Male Frequency Novels 1028 Game Novels
2 Audiobook 104 Male Frequency Novels 1029 Science Fiction
2 Audiobook 105 Female Frequency Novels 1030 Fantasy Romance
2 Audiobook 105 Female Frequency Novels 1031 Romantic Youth
2 Audiobook 105 Female Frequency Novels 1032 Modern Romance
2 Audiobook 105 Female Frequency Novels 1033 Ancient Romance
  1. Sample Code:
@RestController
@RequestMapping(value = "/api/album")
public class BaseCategoryApiController {

    @Autowired
    private BaseCategoryService baseCategoryService;

    @GetMapping("/category/getBaseCategoryList")
    public Result<List<JSONObject>> getBaseCategoryList() {
        List<JSONObject> list = baseCategoryService.getBaseCategoryList();
        return Result.ok(list);
    }
}

public interface BaseCategoryService extends IService<BaseCategory1> {
    List<JSONObject> getBaseCategoryList();
}

@Service
public class BaseCategoryServiceImpl extends ServiceImpl<BaseCategory1Mapper, BaseCategory1> implements BaseCategoryService {

    @Autowired
    private BaseCategory1Mapper baseCategory1Mapper;

    @Autowired
    private BaseCategory2Mapper baseCategory2Mapper;

    @Autowired
    private BaseCategory3Mapper baseCategory3Mapper;

    @Autowired
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Override
    public List<JSONObject> getBaseCategoryList() {
        // Create target collection
        List<JSONObject> allList = new ArrayList<>();

        // Query all category data from view
        List<BaseCategoryView> allCategoryList = baseCategoryViewMapper.selectList(null);

        // Process level 1 categories - group by level 1 category ID
        // Using Stream to group into Map where key=level1Id, value=list of categories
        Map<Long, List<BaseCategoryView>> category1Map =
                allCategoryList.stream()
                        .collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));
        
        if (CollectionUtil.isNotEmpty(category1Map)) {
            for (Map.Entry<Long, List<BaseCategoryView>> entry1 : category1Map.entrySet()) {
                // Process each level 1 category
                Long category1Id = entry1.getKey();
                String category1Name = entry1.getValue().get(0).getCategory1Name();
                
                // Build level 1 category JSON object
                JSONObject jsonObject1 = new JSONObject();
                jsonObject1.put("categoryId", category1Id);
                jsonObject1.put("categoryName", category1Name);
                
                // Process level 2 categories within current level 1 category
                Map<Long, List<BaseCategoryView>> category2Map = entry1.getValue().stream()
                        .collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));
                
                if (CollectionUtil.isNotEmpty(category2Map)) {
                    List<JSONObject> jsonObject2List = new ArrayList<>();
                    
                    for (Map.Entry<Long, List<BaseCategoryView>> entry2 : category2Map.entrySet()) {
                        // Get level 2 category ID and name
                        Long category2Id = entry2.getKey();
                        String category2Name = entry2.getValue().get(0).getCategory2Name();
                        
                        // Build level 2 category JSON object
                        JSONObject jsonObject2 = new JSONObject();
                        jsonObject2.put("categoryId", category2Id);
                        jsonObject2.put("categoryName", category2Name);
                        
                        // Process level 3 categories within current level 2 category
                        List<JSONObject> jsonObject3List = new ArrayList<>();
                        for (BaseCategoryView baseCategoryView : entry2.getValue()) {
                            // Get level 3 category ID and name
                            Long category3Id = baseCategoryView.getCategory3Id();
                            String category3Name = baseCategoryView.getCategory3Name();
                            
                            // Build level 3 category JSON object
                            JSONObject jsonObject3 = new JSONObject();
                            jsonObject3.put("categoryId", category3Id);
                            jsonObject3.put("categoryName", category3Name);
                            jsonObject3List.add(jsonObject3);
                        }
                        
                        // Add level 3 categories to level 2 object's "categoryChild"
                        jsonObject2.put("categoryChild", jsonObject3List);
                        jsonObject2List.add(jsonObject2);
                    }
                    
                    // Add level 2 categories to level 1 object's "categoryChild"
                    jsonObject1.put("categoryChild", jsonObject2List);
                }
                
                // Add level 1 category to final result list
                allList.add(jsonObject1);
            }
        }
        return allList;
    }
}

@Mapper
public interface BaseCategoryViewMapper extends BaseMapper<BaseCategoryView> {

}
  1. Json Format Result:
[
  {
    "categoryName": "Music",
    "categoryId": 1,
    "categoryChild": [
      {
        "categoryName": "Music & Sound Effects",
        "categoryId": 101,
        "categoryChild": [
          {"categoryName": "Hypnotic Music", "categoryId": 1001},
          {"categoryName": "Relaxation Music", "categoryId": 1002},
          {"categoryName": "Energizing Music", "categoryId": 1003},
          {"categoryName": "Prenatal Music", "categoryId": 1004},
          {"categoryName": "Workout Music", "categoryId": 1005},
          {"categoryName": "Leisure Music", "categoryId": 1006}
        ]
      },
      {
        "categoryName": "Guided Courses",
        "categoryId": 102,
        "categoryChild": [
          {"categoryName": "Sleep Guidance", "categoryId": 1007},
          {"categoryName": "Relaxation Guidance", "categoryId": 1008},
          {"categoryName": "Focus Guidance", "categoryId": 1009},
          {"categoryName": "Children's Sleep Guidance", "categoryId": 1010},
          {"categoryName": "Others", "categoryId": 1011}
        ]
      },
      {
        "categoryName": "Host Music Programs",
        "categoryId": 103,
        "categoryChild": [
          {"categoryName": "Classic Music Recommendations", "categoryId": 1012},
          {"categoryName": "Hot Songs Highlights", "categoryId": 1013},
          {"categoryName": "Song Covers", "categoryId": 1014},
          {"categoryName": "Music Lessons", "categoryId": 1015},
          {"categoryName": "Music Stories", "categoryId": 1016}
        ]
      }
    ]
  },
  {
    "categoryName": "Audiobooks",
    "categoryId": 2,
    "categoryChild": [
      {
        "categoryName": "Male-Oriented Fiction",
        "categoryId": 104,
        "categoryChild": [
          {"categoryName": "Military Fiction", "categoryId": 1018},
          {"categoryName": "Sports Fiction", "categoryId": 1019},
          {"categoryName": "N-Dimensional", "categoryId": 1020},
          {"categoryName": "Fantasy", "categoryId": 1021},
          {"categoryName": "Suspense & Supernatural", "categoryId": 1022},
          {"categoryName": "Martial Arts", "categoryId": 1023},
          {"categoryName": "Urban Fiction", "categoryId": 1024},
          {"categoryName": "Xianxia", "categoryId": 1025},
          {"categoryName": "Xuanhuan", "categoryId": 1026},
          {"categoryName": "Historical Fiction", "categoryId": 1027},
          {"categoryName": "Gaming Fiction", "categoryId": 1028},
          {"categoryName": "Science Fiction", "categoryId": 1029}
        ]
      },
      {
        "categoryName": "Female-Oriented Fiction",
        "categoryId": 105,
        "categoryChild": [
          {"categoryName": "Fantasy Romance", "categoryId": 1030},
          {"categoryName": "Youth Romance", "categoryId": 1031},
          {"categoryName": "Modern Romance", "categoryId": 1032},
          {"categoryName": "Historical Romance", "categoryId": 1033}
        ]
      }
    ]
  }
]


Paging Query, Dynamic Query, Row-to-Column Transformation

SHOW CONTENTS

ER Diagram: album_info &amp; album_stat

  1. album_info Table:
id album_title cover_url include_track_count create_time
1 Night Piano Melodies https://imagev2...xxx.png 55 2023-04-04 09:05:02
2 Classic Gramophone https://imagev2...yyy.png 55 2023-04-04 09:05:27
  1. album_stat Table:
album_id stat_type stat_num
1 0401 1
1 0402 0
1 0403 0
1 0404 999
2 0401 1
2 0402 0
2 0403 0
2 0404 0
  1. Sample Code:
@RestController
@RequestMapping("api/album")
public class AlbumInfoApiController {
    @PostMapping("/albumInfo/findUserAlbumPage/{page}/{limit}")
    @TingshuLogin
    public Result<Page<AlbumListVo>> getUserAlbumPage(@RequestBody AlbumInfoQuery albumInfoQuery,
                                                    @PathVariable int page,
                                                    @PathVariable int limit) {
        // Get user ID and set it to query object
        Long userId = AuthContextHolder.getUserId();
        albumInfoQuery.setUserId(userId);

        // Use MyBatisPlus pagination at controller level
        Page<AlbumListVo> pageInfo = new Page<>(page, limit);
        pageInfo = albumInfoService.getUserAlbumPage(pageInfo, albumInfoQuery);
        return Result.ok(pageInfo);
    }
}

public interface AlbumInfoService extends IService<AlbumInfo> {
    Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery);
}

@Service
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {
    @Override
    public Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery) {
        return albumInfoMapper.getUserAlbumPage(pageInfo, albumInfoQuery);
    }
}

@Mapper
public interface AlbumInfoMapper extends BaseMapper<AlbumInfo> {
    Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, @Param("vo") AlbumInfoQuery albumInfoQuery);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="dev.signalyu.tingshu.album.mapper.AlbumInfoMapper">
    <select id="getUserAlbumPage" resultType="dev.signalyu.tingshu.vo.album.AlbumListVo">
        select
        ai.id albumId,
        ai.album_title,
        ai.cover_url,
        ai.include_track_count,
        ai.create_time,
        max(if(stat.stat_type = '0401', stat_num, 0)) playStatNum,
        max(if(stat.stat_type = '0402', stat_num, 0)) subscribeStatNum,
        max(if(stat.stat_type = '0403', stat_num, 0)) buyStatNum,
        max(if(stat.stat_type = '0404', stat_num, 0)) commentStatNum
        from album_info ai
        inner join album_stat stat
        on stat.album_id = ai.id
        <where>
            <if test="vo.userId != null">
                ai.user_id = #{vo.userId}
            </if>
            <if test="vo.status != null and vo.status != ''">
                and ai.status = #{vo.status}
            </if>
            <if test="vo.albumTitle != null and vo.albumTitle != ''">
                and ai.album_title like concat('%', #{vo.albumTitle} ,'%')
            </if>
        </where>
        and ai.is_deleted = 0
        group by ai.id
        order by ai.id desc
    </select>
</mapper>
  1. Result Sets:
albumId album_title cover_url include_track_count create_time playStatNum subscribeStatNum buyStatNum commentStatNum
1 Night Piano Melodies https://imagev2...xxx.png 55 2023-04-04 09:05:02 1 0 0 999
2 Classic Gramophone https://imagev2...yyy.png 55 2023-04-04 09:05:27 1 0 0 0
  • MyBatis-Plus dynamically modifies SQL statements for pagination using the PaginationInnerInterceptor. The standard implementation flow is as follows:
// Configuration Setup
@Configuration
@MapperScan("dev.signalyu.tingshu.*.mapper")
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // Add pagination interceptor with MySQL dialect
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

// Pagination Query Execution
public class AlbumInfoApiController {
    Page<AlbumListVo> pageInfo = new Page<>(page, limit);
    pageInfo = albumInfoService.getUserAlbumPage(pageInfo, albumInfoQuery);
}

public interface AlbumInfoService extends IService<AlbumInfo> {
    Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery);
}

public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {
    @Override
    public Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery) {
        return albumInfoMapper.getUserAlbumPage(pageInfo, albumInfoQuery);
    }
}

public interface AlbumInfoMapper extends BaseMapper<AlbumInfo> {
    Page<AlbumListVo> getUserAlbumPage(Page<AlbumListVo> pageInfo, @Param("vo") AlbumInfoQuery albumInfoQuery);
}
  • Dynamic Query in MyBatis and MyBatisPlus
--- MyBatis ---
<where>
    <if test="vo.userId != null">
        ai.user_id = #{vo.userId}
    </if>
    <if test="vo.status != null and vo.status != ''">
        and ai.status = #{vo.status}
    </if>
    <if test="vo.albumTitle != null and vo.albumTitle != ''">
        and ai.album_title like concat('%', #{vo.albumTitle} ,'%')
    </if>
</where>
       
--- MyBatisPlus ---
QueryWrapper<AlbumInfo> queryWrapper = new QueryWrapper<>();
// "vo" represents an object of AlbumInfoQuery class
queryWrapper.eq(vo.getUserId() != null, "user_id", vo.getUserId())
            .eq(vo.getStatus() != null && !vo.getStatus().isEmpty(), "status", vo.getStatus())
            .like(vo.getAlbumTitle() != null && !vo.getAlbumTitle().isEmpty(), "album_title", vo.getAlbumTitle())
            .eq("is_deleted", 0)
            .orderByDesc("id");
albumInfoService.list(queryWrapper);
  • Row-to-Column Transformation
--- Use MAX() and IF() ---
select ai.id AS albumId,
       ai.album_title,
       ai.cover_url,
       ai.include_track_count,
       ai.create_time,
       --- Even though IF() returns only one value (0 or stat_type), --- 
       --- it is necessary to use an aggregate function like MAX() to ---
       --- ensure proper behavior in the GROUP BY clause ---
       max(if(stat.stat_type = '0401', stat_num, 0)) playStatNum,
       max(if(stat.stat_type = '0402', stat_num, 0)) subscribeStatNum,
       max(if(stat.stat_type = '0403', stat_num, 0)) buyStatNum,
       max(if(stat.stat_type = '0404', stat_num, 0)) commentStatNum
from album_info ai
         inner join album_stat stat
                    on stat.album_id = ai.id
group by ai.id;

--- Use MAX() and CASE...WHEN ---
SELECT 
    ai.id AS albumId,
    ai.album_title,
    ai.cover_url,
    ai.include_track_count,
    ai.create_time,
    MAX(CASE WHEN stat.stat_type = '0401' THEN stat.stat_num ELSE 0 END) AS playStatNum,
    MAX(CASE WHEN stat.stat_type = '0402' THEN stat.stat_num ELSE 0 END) AS subscribeStatNum,
    MAX(CASE WHEN stat.stat_type = '0403' THEN stat.stat_num ELSE 0 END) AS buyStatNum,
    MAX(CASE WHEN stat.stat_type = '0404' THEN stat.stat_num ELSE 0 END) AS commentStatNum
FROM 
    album_info ai
INNER JOIN 
    album_stat stat ON stat.album_id = ai.id
GROUP BY 
    ai.id, ai.album_title, ai.cover_url, ai.include_track_count, ai.create_time;

--- Use GROUP_CONCAT() and IF() ---
SELECT 
       ai.id AS albumId,
       ai.album_title,
       ai.cover_url,
       ai.include_track_count,
       ai.create_time,
       GROUP_CONCAT(IF(stat.stat_type = '0401', stat.stat_num, 0) ORDER BY stat.stat_type) AS playStatNum,
       GROUP_CONCAT(IF(stat.stat_type = '0402', stat.stat_num, 0) ORDER BY stat.stat_type) AS subscribeStatNum,
       GROUP_CONCAT(IF(stat.stat_type = '0403', stat.stat_num, 0) ORDER BY stat.stat_type) AS buyStatNum,
       GROUP_CONCAT(IF(stat.stat_type = '0404', stat.stat_num, 0) ORDER BY stat.stat_type) AS commentStatNum
FROM album_info ai
         INNER JOIN album_stat stat
                    ON stat.album_id = ai.id
GROUP BY ai.id;

It is important to note that when performing row-to-column transformation, applying an aggregate function such as MAX() or GROUP_CONCAT() is necessary. Otherwise, MySQL will raise an exception similar to “nonaggregated column is not functionally dependent on columns in GROUP BY clause.” For example, running the following query will result in an error message like: SELECT list is not in GROUP BY clause and contains nonaggregated column 'tingshu_album.stat.stat_type' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by.

SELECT ai.id AS albumId,
       ai.album_title,
       ai.cover_url,
       ai.include_track_count,
       ai.create_time,
       if(stat.stat_type = '0401', stat_num, 0) AS playStatNum,
       if(stat.stat_type = '0402', stat_num, 0) AS subscribeStatNum,
       if(stat.stat_type = '0403', stat_num, 0) AS buyStatNum,
       if(stat.stat_type = '0404', stat_num, 0) AS commentStatNum
FROM album_info ai
         INNER JOIN album_stat stat
                    ON stat.album_id = ai.id
GROUP BY ai.id; 


Database


Caching