Code generator

Posted by cfemocha on Tue, 07 Jan 2020 02:41:56 +0100

Talk about the code generator, some technical solutions, details

Interface Overview

Checklist

Code structure

Generic code

target

In order to simplify the code and generate template code, we have code generator.

premise

The premise of code generator is that there are already some templated and standardized code. Such as general DAO layer, Service layer, and even
Controller layer.

Technical means

Basic functions

  • Select a familiar template engine, such as freemark, to read the database field information and generate relevant code according to the template.
  • Provide a friendly interface to check the required table and support search

Optimization experience

  • Table supports custom entity mapping
  • Support for external data sources
  • Edit template files online

Technology realization

Here I develop a Java code generator based on spring boot and jetbrick template engine according to the company's technology stack.

Support

  • Online preview table, check the required table
  • Specify package prefix, table prefix and other auto generated codes, and support entity name customization
  • Embedded sqlite database, without additional configuration, directly start the project.
  • Connect external data sources (automatic switching of multiple data sources) and generate template code
  • Simplify template code with lombok

Among them, the highlight is the embedded sqlite database to maintain the source information. Read the external data source, and the data source switches automatically. Other functions are relatively conventional. I won't elaborate here.

External data source

First, we need to maintain the connection information of the external data source, and then if the implementation is short, read the structure information of the external data source table through JDBC template and so on, and finally render according to the template.

But it's not cool!

Data source switching

Can you write the code to read the table structure information and automatically switch the connection according to a data source information selected by the front end?

Tolerable!

spring jdbc's org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource inspired me. The Taoists who are familiar with it should know how to operate it. They don't know a lot about the Internet.

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    @Nullable
    private Map<Object, Object> targetDataSources;

    @Nullable
    private Object defaultTargetDataSource;

    private boolean lenientFallback = true;

    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

    @Nullable
    private Map<Object, DataSource> resolvedDataSources;

    @Nullable
    private DataSource resolvedDefaultDataSource;

    // Omit other codes
}

But there is a problem that you need to define each data source in the configuration file or on the program in advance! It is still not dynamic enough to configure maintenance on the interface. So I defined a dynamic data source:

/**
 * Dynamic data source
 *
 * @author byebye disco
 * @since 1.0
 */
@Slf4j
public class DynamicDataSource extends AbstractDataSource implements DataSourceManager {
    
    /**
     * Maintain correspondence between data source ID and data source
     */ 
    private final ConcurrentMap<Long, DataSource> dataSources;

    /**
     * The default data source is the embedded sqlite data source
     */ 
    private DataSource defaultDataSource;

    public DynamicDataSource() {
        dataSources = new ConcurrentHashMap<>();
    }

    public void setDefaultTargetDataSource(DataSource defaultTargetDataSource) {
        this.defaultDataSource = defaultTargetDataSource;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return (T) this;
        }
        return determineTargetDataSource().unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
    }

    /**
     * It is mainly based on this method to decide which data source to obtain
     * Put the data source ID through ThreadLocal and get the data source from the map
     */ 
    private DataSource determineTargetDataSource() {
        Long lookupKey = DataSourceContextHolder.getKey();
        DataSource dataSource = Optional.ofNullable(lookupKey)
                .map(dataSources::get)
                .orElse(defaultDataSource);
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

    /**
     * Update the internal map maintenance information when updating the interface or adding a new data source
     */ 
    @Override
    public void put(Long id, DataSource dataSource) {
        log.info("put datasource: {}", id);
        dataSources.put(id, dataSource);
    }

    @Override
    public DataSource get(Long id) {
        return dataSources.get(id);
    }

    /**
     * Delete internal map maintenance information when deleting data source in the interface
     */ 
    @Override
    public void remove(Long id) {
        log.warn("remove datasource: {}", id);
        dataSources.remove(id);
    }

}

See here, you Taoist friends should be a shock tiger body, know how to move.

/**
 * Data source key context management
 *
 * @author byebye disco
 * @since 1.0
 */
public class DataSourceContextHolder {

    private static final ThreadLocal<Long> THREAD_LOCAL = new ThreadLocal<>();

    private DataSourceContextHolder() {
        throw new IllegalStateException("Utils");
    }

    public static synchronized void setKey(Long key) {
        THREAD_LOCAL.set(key);
    }

    public static Long getKey() {
        return THREAD_LOCAL.get();
    }

    public static void clearKey() {
        THREAD_LOCAL.remove();
    }

}

In the code below, switch the data source:

@Override
public PageInfo<Table> getTables(Long dataSourceId, String database, String table, IPage page) {
    try {
        // Set the data source ID selected on the page to ThreadLocal
        DataSourceContextHolder.setKey(dataSourceId);
        // Here is the table information of paging query data source. mybatis pagehelper is used here
        PageHelper.startPage(page);
        return PageInfo.of(tableRepository.getTables(database, table));
    } finally {
        // Finally, release ThreadLocal. Remember to avoid affecting the default data source. Ha ha
        DataSourceContextHolder.clearKey();
    }
}

Of course, let's write a little more here. You can use AOP to generalize part of the ThreadLocal code.

Reflection:

  • After the program is started, the data source defined in the database needs to be maintained in the map information

About open source

I don't think it's necessary to open source. Why don't you make one by yourself?

Topics: Java Database SQLite JDBC Spring