takeaway

Having introduced the hierarchical structure of the COLA framework in the previous article, this article needs to develop a basic framework that provides the introduction of third-party JAR packages, generic DTO, DO, Converter, Mybatis encapsulation, and more

The module planning

Module name instructions
dependencies Commonly used third party JAR package introduction and version number declaration
base Base module that provides utility classes, assertions, and general exception classes
client Provides external Dtos, Queries, and Cmd
domain Provides several value objects, entity classes, and aggregate root interfaces
infra Provide basic DO class, Mybatis encapsulation and so on

For the convenience of writing this article, the package names in each module are no longer divided, you can divide, below create the above modules in turn.

The groupdId of the project is com.ahydd.ddd, which can be modified by everyone.

Dependencies module

This module simply adds a pom.xml file, brings in the usual third-party JAR, declares the version number, and then no more searching for the version number in other modules and projects.


      
<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.ahydd.ddd</groupId>
    <artifactId>dependencies</artifactId>
    <version>1.0.0 - the SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <druid.version>1.1.10</druid.version>
        <mysql.version>8.0.16</mysql.version>
        <mybatis.version>1.3.0</mybatis.version>
        <tk.mybatis.version>2.0.2</tk.mybatis.version>
        <jackson.version>2.11.2</jackson.version>
        <guava.version>20.0</guava.version>
        <mapstruct.version>1.4.2. The Final</mapstruct.version>
        <testable.version>0.4.9</testable.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>

            <! -- database -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${tk.mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.2.5</version>
            </dependency>

            <! -- jackson -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>${jackson.version}</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-annotations</artifactId>
                <version>${jackson.version}</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>${jackson.version}</version>
            </dependency>

            <! -- swagger -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-boot-starter</artifactId>
                <version>3.0.0</version>
                <exclusions>
                    <exclusion>
                        <groupId>io.swagger</groupId>
                        <artifactId>swagger-models</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-models</artifactId>
                <version>1.5.22</version>
            </dependency>

            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>${mapstruct.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.testable</groupId>
                <artifactId>testable-all</artifactId>
                <version>${testable.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
Copy the code

The base module

Parent is specified in pom.xml. What else is there to say


      
<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.ahydd.ddd</groupId>
        <artifactId>parent</artifactId>
        <version>1.0.0 - the SNAPSHOT</version>
    </parent>

    <artifactId>base</artifactId>
    <packaging>jar</packaging>

</project>
Copy the code

Let’s write a few common classes that we need to use

PageQo

Used to hold client request parameters for paging and sorting

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageQo {
    private Integer page;
    private Integer pageSize;
    private String orderBy;
    private String orderDir;

    public PageQo(Integer page, Integer pageSize) {
        this.page = page;
        this.pageSize = pageSize; }}Copy the code

OrderQo

This is used to hold the sorted request parameters in the client request, which does not allow paging

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderQo {
    public static final String ASC = "asc";
    public static final String DESC = "desc";

    private String orderBy;
    private String orderDir;
}
Copy the code

Pager

For paging data, you need to store the resulting collection and paging information, such as pages, items per page, total records, and pages

@Data
@NoArgsConstructor
public class Pager<T> implements Serializable {
    private static final long serialVersionUID = -2033174573373926746L;
    public static final int DEFAULT_PAGE_SIZE = 10;
    public static final int MAX_PAGE_SIZE = 100;

    private PageInfo page;
    private List<T> list;

    public Pager(int page, int pageSize, int recordCount, List<T> list) {
        this.page = new PageInfo(page, pageSize, recordCount);
        this.list = list;
    }

    public static void initPageQo(PageQo pageQo) {
        if (pageQo.getPage() == null) {
            pageQo.setPage(1);
        }
        if (pageQo.getPageSize() == null) {
            pageQo.setPageSize(DEFAULT_PAGE_SIZE);
        }
        pageQo.setPage(Math.max(pageQo.getPage(), 1));
        pageQo.setPageSize(Math.min(pageQo.getPageSize(), MAX_PAGE_SIZE));
    }

    @Data
    public static class PageInfo {
        private int page;
        private int pageSize;
        private int pageCount;
        private int recordCount;

        public PageInfo(int page, int pageSize, int recordCount) {
            this.page = Math.max(page, 1);
            this.pageSize = pageSize < 1 ? DEFAULT_PAGE_SIZE : pageSize;
            this.pageSize = Math.min(this.pageSize, MAX_PAGE_SIZE);
            this.recordCount = recordCount;

            if (recordCount == 0 || pageSize == 0) {
                this.pageCount = 0;
            } else {
                this.pageCount = (int) Math.ceil((double) recordCount / pageSize); }}}}Copy the code

abnormal

For exceptions, we define a basic generic exception class, which then facilitates uniform exception handling

public class CommonException extends RuntimeException {
    public CommonException(String msg) {
        super(msg); }}Copy the code

assertions

Many methods have been removed to reduce the length of this article, and you can add more

public class Assert {
    public static void isNull(Object obj, String errorMsg) {
        if (obj == null) {
            throw newCommonException(errorMsg); }}public static void isEmpty(String data, String errorMsg) {
        if (StringUtils.isEmpty(data)) {
            throw newCommonException(errorMsg); }}public static void isEmpty(Collection
        data, String errorMsg) {
        if (data == null || data.isEmpty()) {
            throw newCommonException(errorMsg); }}public static void isTrue(boolean expression, String errorMsg) {
        if (expression) {
            throw newCommonException(errorMsg); }}public static void isFalse(boolean expression, String errorMsg) {
        if(! expression) {throw newCommonException(errorMsg); }}}Copy the code

StringUtils

Provides a string processing utility class that can write in methods such as case first, dash, and underscore hump

public class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0; }}Copy the code

The client module

This module mainly defines the basic Query, Cmd, and Dto, where Query is used for GET requests, Cmd for writing requests such as POST, PUT, and DELETE requests, and Dto for output. In general, you need to implement Serializable, but this is not done for convenience in this article, so you can add your own code.

Pom.xml is simple, just as the base module specifies the parent

Every table in my plan has an ID field, and it is of type Long. Both Query and Cmd need to pass in an ID, so there is only one ID field in both the base Query and Cmd

The Query and Cmd

@Data
public class BaseQuery {
    private Long id;
}
Copy the code

BaseCmd is the same as BaseQuery, just with a different name, so no copy code

BaseListQuery is used to query a list, so all you need to do is specify the sort, as follows:

@Data
public class BaseListQuery extends BaseQuery {
    private String orderBy;
    private String orderDir;
}
Copy the code

BasePageQuery is used to query a paged list. You can inherit from BaseListQuery and add two paged fields, as follows:

@Data
public class BasePageQuery extends BaseListQuery {
    private Integer page;
    private Integer pageSize;
}
Copy the code

DTO

PagerDto is used to output paging data

@Data
public class PagerDto<T> {
    private List<T> list;
    private Pager.PageInfo page;
}
Copy the code

ListDto is used to output list data without paging

@Data
public class ListDto<T> {
    private List<T> list;
}
Copy the code

Result is used to unify the output format and provides two static methods, SUCCESS and error, to facilitate the construction of the Result object

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    public static int SUCCESS = 0;
    public static int ERROR = 1;
    
    public static final String MSG_OK = "OK";

    private Integer code;
    private String msg;
    private T data;

    public static <T> Result<T> success(String msg) {
        return new Result<>(Result.SUCCESS, msg, null);
    }

    public static <T> Result<T> success(String msg, T data) {
        return new Result<>(Result.SUCCESS, msg, data);
    }

    public static <T> Result<T> error(String msg) {
        return new Result<>(Result.ERROR, msg, null);
    }

    public static <T> Result<T> error(String msg, T data) {
        return newResult<>(Result.ERROR, msg, data); }}Copy the code

The Result > output JSON format is as follows:

{
    "code": 0."msg": "xxx"."data": {
        "list": []."page": {
            "page": 1."pageSize": 10."pageCount": 1."recordCount": 8}}}Copy the code

Domain module

The Domain module needs to define some ID value objects, such as LongId and UserId, as well as entities and aggregate roots, which depend on some of the utility classes provided by the Base module and do not depend on any other module

An ID interface Identifier is defined, and then all ID value objects are implemented from that interface

public interface Identifier {}Copy the code

Define two ID value objects, LongId and UserId. This will be used in any system, so it is defined directly in the framework base. Due to length, the value objects such as UserName, Password, and Revision are not included

@Getter
@ToString
public class UserId implements Identifier {
    private final Long id;

    public UserId(Long id) {
        Assert.isNullOrLessThanZero(id, "Incorrect user ID");
        this.id = id;
    }

    public static Optional<UserId> of(Long id) {
        if (id == null) {
            return Optional.empty();
        }
        return Optional.of(newUserId(id)); }}Copy the code

The “of” method is used to simplify external calls. The value object is either null or valid. So when the “of” method is passed as an argument =null, it returns option.empty (). = Null.

Once you have the value object, you can continue to define the entity-related interface to specify that the entity class must have the getId method, and then all entity class interfaces inherit from that interface

public interface Identifiable<ID extends Identifier> {
    ID getId(a);
}
Copy the code

Define the Entity class interface Entity inherits from the Identifiable interface, and Entity is used for entities that are not the aggregate root, such as orders and order items, for which the order is the aggregate root and the order item is the Entity

public interface Entity<ID extends Identifier> extends Identifiable<ID> {}Copy the code

The Aggregate root interface is defined as Aggregate

public interface Aggregate<ID extends Identifier> extends Entity<ID> {}Copy the code

Now that the interface between the aggregate root and the entity is defined, we can define the underlying aggregate root and entity class. We specify that all entity classes have the following fields

The field name instructions
revision The version number
createdBy Creator ID
createdTime Creation time
updatedBy Update the ID
updatedTime Update time
isDelete Whether or not to delete
deletedBy Delete the ID
deletedTime Delete the time

Note: These fields are fields of the domain entity class and do not correspond to fields in the database table

Together with the above fields, define the underlying entity class

@Data
public class BaseEntity<ID extends Identifier> implements Entity<ID> {
    protected ID id;

    protected Integer revision;
    protected UserId createdBy;
    protected Integer createdTime;
    protected UserId updatedBy;
    protected Integer updatedTime;
    protected Boolean isDelete;
    protected UserId deletedBy;
    protected Integer deletedTime;

    /** * Initialize the default field * of the object@paramUserId Operator ID */
    public void init(UserId userId) {
        intNowTime = current timestamp;if (id == null) {
            revision = 0;
            createdBy = userId;
            createdTime = nowTime;
            isDelete = false;
        } else{ updatedBy = userId; updatedTime = nowTime; }}}Copy the code

The base aggregate root inherits directly from BaseEntity

@Data
public class BaseAggregate<ID extends Identifier> extends BaseEntity<ID> implements Aggregate<ID> {}Copy the code

All Converters and Assembler inherit from this interface. MapStruct automatically calls these default methods for conversion. For example, if you need to convert a UserId object to a Long, The fromUserId method is automatically called

public interface Converter {
    default Long fromUserId(UserId id) {
        if (id == null) {
            return null;
        }
        return id.getId();
    }

    default UserId toUserId(Long id) {
        return UserId.of(id).orElse(null); }}Copy the code

Same thing with the LongId method, I’m not going to do it here

Infra module

With all the manipulation of data at this level, POM.xml needs to introduce dependencies


      
<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.ahydd.ddd</groupId>
        <artifactId>parent</artifactId>
        <version>1.0.0 - the SNAPSHOT</version>
    </parent>

    <artifactId>infra</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.ahydd.ddd</groupId>
            <artifactId>client</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.ahydd.ddd</groupId>
            <artifactId>domain</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>
Copy the code

Define a Mapper that inherits from tk.mybatis, which allows you to write your own methods to generate SQL statements. For example, add a replace method to implement the replace function in MySQL

@RegisterMapper
public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T>, AggregationMapper<T> {
    @UpdateProvider(type = BaseProvider.class, method = "dynamicSQL")
    int replace(String fieldName, String oldValue, String newValue, Example example);
}
Copy the code

The code that implements the replace function

public class BaseProvider extends MapperTemplate {
    public BaseProvider(Class
        mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
    }

    public String replace(MappedStatement ms) {
        finalClass<? > entityClass = getEntityClass(ms); StringBuilder sb =new StringBuilder();

        if (getConfig().isSafeUpdate()) {
            sb.append(SqlHelper.exampleHasAtLeastOneCriteriaCheck("example"));
        }

        // Note that this is a concatenated SQL statement, so the fieldName must be made sure that there is no possibility of injection. When we use this fieldName, we must write it by hand
        sb.append(SqlHelper.updateTable(entityClass, tableName(entityClass)));
        sb.append(" set `${fieldName}`=replace(`${fieldName}`, #{oldValue}, #{newValue})");
        sb.append(SqlHelper.updateByExampleWhereClause());
        returnsb.toString(); }}Copy the code

The BaseProvider SQL statement uses the ${} concatenation, but fieldName is written in the code and is not injected, as in the comment. Update XXX set XXX =replace(XXX, ‘a’, ‘b’) where XXX = XXX

Next, define a BaseDo class, which is the base class for all subsequent Data Objects, and specify that every table must have these fields

@Data
public class BaseDo {
    /** * ID */
    @Id
    @Column(name = "id")
    @GeneratedValue(generator = "JDBC")
    private Long id;

    /** * Optimistic lock */
    @Column(name = "revision")
    private Integer revision;

    /** * founder */
    @Column(name = "created_by")
    private Long createdBy;

    /** * Creation time */
    @Column(name = "created_time")
    private Integer createdTime;

    /** * Updater */
    @Column(name = "updated_by")
    private Long updatedBy;

    /** * Update time */
    @Column(name = "updated_time")
    private Integer updatedTime;

    /** * Delete the id */
    @Column(name = "is_delete")
    private Boolean isDelete;

    /**
     * 删除人
     */
    @Column(name = "deleted_by")
    private Long deletedBy;

    /** * Delete time */
    @Column(name = "deleted_time")
    private Integer deletedTime;
}
Copy the code

Finally, BaseDaoImpl class is defined to encapsulate the common operations of Mybatis for convenient use

public class BaseDaoImpl<T extends BaseDo.D extends BaseMapper<T>> {
    @Autowired
    private D mapper;

    public List<T> list(Example example) {
        setDefaultExample(example);
        return mapper.selectByExample(example);
    }

    public Pager<T> page(Example example, PageQo pageQo) {
        setDefaultExample(example);
        Pager.initPageQo(pageQo);

        PageHelper.startPage(pageQo.getPage(), pageQo.getPageSize());
        List<T> list = mapper.selectByExample(example);
        PageInfo<T> pageInfo = new PageInfo<>(list);
        return new Pager<>(pageQo.getPage(), pageQo.getPageSize(), (int) pageInfo.getTotal(), pageInfo.getList());
    }

    public int count(Example example) {
        setDefaultExample(example);
        return mapper.selectCountByExample(example);
    }

    public T findOne(Example example) {
        setDefaultExample(example);
        RowBounds rowBounds = new RowBounds(0.1);
        List<T> list = mapper.selectByExampleAndRowBounds(example, rowBounds);
        if(list ! =null && !list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    public T insert(T t) {
        mapper.insert(t);
        return t;
    }

    public int insertList(List<T> ts) {
        return mapper.insertList(ts);
    }

    public T update(T t) {
        mapper.updateByPrimaryKey(t);
        return t;
    }

    public int delete(T t) {
        return mapper.delete(t);
    }

    public void setDefaultExample(Example example) {
        example.and().andEqualTo("isDelete".false); }}Copy the code

Only some of the most common method wrappers are written. There are too many other methods to send

Now that the basic framework is ready to use, the next article will set up the IDEA plug-in development environment, development plan, and basic terminology

If you like it, please like it. Thank you