IDEA
Postfix Completion
Postfix completion allows writing code faster by adding expressions after a variable or value. It can be customized to suit specific coding needs.SHOW CONTENTS
"Hello".var → String s = "Hello";
Live Templates
Live Templates are predefined code snippets that can be inserted quickly using a shortcut. It can be customized to suit specific coding needs.SHOW CONTENTS
sout → System.out.println();
Keyboard Shortcuts
SHOW CONTENTS
Alt + Enter
Ctrl + P
Ctrl + Alt + J
Shift + Enter
Ctrl + Alt + Enter
Ctrl + J
Shift + F6
Ctrl + Alt + M
Ctrl + O
Ctrl + I
Ctrl + Alt + O
Ctrl + N
→ Ctrl + F12
Ctrl + Alt + ←
Ctrl + Alt + →
Ctrl + H
Ctrl + Q
Ctrl + Alt + U
Ctrl + G
→ Enter line numberCtrl + Alt + B
Ctrl + Shift + +
Ctrl + Shift + -
Debug
Method Breakpoints
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 SHOW CONTENTS
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
Field breakpoints are a special type of breakpoint that trigger when a specific field is accessed or modified. Below is a real world example:SHOW CONTENTS
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
Conditional breakpoints pause execution only when a specified condition is true. Below is a sample example:SHOW CONTENTS
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
Exception breakpoints pause execution when specified exceptions are thrown, even if they’re caught and handled. Below is a sample example:SHOW CONTENTS
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
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:SHOW CONTENTS
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
Custom data views allow controlling how objects are displayed in the debugger, making complex objects easier to inspect.SHOW CONTENTS
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
https://youtu.be/spto4kcMHUI?si=_vsBnV3JElTcvhCZ
https://youtu.be/rUZZTCTpHCQ?si=isejuUB7irsGIU3Z
https://youtu.be/h6Ld1N9ZzX8?si=BckuYaAd0qDl_QnR
Maven
Introduction to Maven
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 (SHOW CONTENTS
pom.xml
) at the root of the project.
settings.xml
Configuration
The 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
├── ...
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.
<!-- 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>
<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>
<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
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.SHOW CONTENTS
<groupId>com.example</groupId>
or <groupId>dev.signalyu</groupId>
.<artifactId>my-app</artifactId>
.<version>1.0.0</version>
.<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
When Maven fails to download dependencies, follow these steps to resolve the issue:SHOW CONTENTS
settings.xml
to ensure it’s correctly set up.pom.xml
.File -> Invalidate Caches
to clear any cached information that might be causing issues. Then, restart the IntelliJ IDEA.
Dependency Inheritence
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. In the above code, The The difference between SHOW CONTENTS
<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>
<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>
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.
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
@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;
}
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
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.
column="id"
in result set maps to the property="id"
field in BaseAttribute
.column="base_attribute_value_id"
inside the collection
tag maps to the property="id"
field in BaseAttributeValue
.
select
statement inside the mapper doesn’t query attribute_id
field. When mybatis performs mapping, the attributeId
in BaseAttributeValue
objects will be null
.
Update
The SHOW CONTENTS
@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);
}
}
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
Views are useful in several scenarios, including when complex queries are frequently used or when providing controlled access to specific data for security purposes.SHOW CONTENTS
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
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
@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> {
}
[
{
"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
It is important to note that when performing row-to-column transformation, applying an aggregate function such as SHOW CONTENTS
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
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
@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>
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
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);
}
--- 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);
--- 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;
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;