論實現自己的 mybatis,並基於此編寫代碼生成工具
實現基於註解的 mybatis
關於這個問題,請大家移步我的個人博客(http://www.kfyty.com/info.html?article=29),就不在這裏重新寫一遍了。當然博客裏的是第一版的實現,經過多次迭代,最新版本已經面目全非,這裏放上我的 github 地址,感興趣的可以去看一下:https://github.com/kfyty/kfyty-utils
這裏就列一下已經實現的功能吧
- 實現了基於註解的映射,主要有三個註解:@Query,@Execute,@Param
- @Query 內支持子查詢註解(@SubQuery),可以用來爲實體內的某個其他實體編寫 sql 來查詢數據(可惜 java 不支持循環註解)
- @Query,@Execute,@SubQuery 內均支持 @ForEach 註解,用來拼接 sql 語句
- 自動推斷返回值類型,包括基本數據類型及其數組、List、Set、Map<Key, Entity>、Map<Object, Object>、List<Map<Object, Object>>
- 支持重複註解,每個註解的結果集將放入一個 List 返回
- 支持 #{}、${} 操作符,操作符中的表達式支持
‘.’
操作符,以訪問對象屬性 - sql 語句中的別名支持
‘.’
操作符,以支持直接給對象中的某個對象的屬性賦值
使用實現的 mybatis 編寫自己的代碼生成工具
基本使用演示
1、使用內置的 java 模板類生成
- 首先創建一張測試表,如圖:
- 編寫數據源配置類:
這裏的@Configuration 和 @Bean 註解,以及下面的 @AutoWired 註解,不是 Spring 的註解,是自定義的,自己實現的(PS:關於數據源配置文件大家自己添加就好)
package com.kfyty.demo.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.kfyty.configuration.annotation.Bean;
import com.kfyty.configuration.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import java.util.Properties;
/**
* 功能描述: 數據源配置
*
* @author [email protected]
* @date 2019/9/07 21:38
* @since JDK 1.8
*/
@Slf4j
@Configuration
public class DataSourceConfig {
private static final String PATH = "/druid.properties";
@Bean
public DataSource getDruidDataSource() {
try {
Properties properties = new Properties();
properties.load(DataSourceConfig.class.getResourceAsStream(PATH));
return DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
log.error("load properties failed:", e);
}
return null;
}
}
- 編寫生成代碼配置類:
package com.kfyty.demo.config;
import com.kfyty.configuration.annotation.AutoWired;
import com.kfyty.configuration.annotation.Configuration;
import com.kfyty.generate.configuration.GenerateConfiguration;
import com.kfyty.generate.configuration.annotation.BasePackage;
import com.kfyty.generate.configuration.annotation.DataBase;
import com.kfyty.generate.configuration.annotation.DataBaseMapper;
import com.kfyty.generate.configuration.annotation.FilePath;
import com.kfyty.generate.configuration.annotation.GenerateTemplate;
import com.kfyty.generate.configuration.annotation.Table;
import com.kfyty.generate.database.MySQLDataBaseMapper;
import com.kfyty.generate.template.entity.EntityTemplate;
import javax.sql.DataSource;
@Configuration
@DataBase("test")
@Table("user")
@DataBaseMapper(MySQLDataBaseMapper.class)
@FilePath("D:/temp/code-generate")
@BasePackage("com.kfyty")
@GenerateTemplate(EntityTemplate.class)
public class GenerateConfig implements GenerateConfiguration {
@AutoWired
private DataSource dataSource;
@Override
public DataSource getDataSource() {
return this.dataSource;
}
}
- 編寫 Main 方法,開始生成代碼:
package com.kfyty.demo;
import com.kfyty.KfytyApplication;
import com.kfyty.configuration.annotation.EnableAutoGenerateSources;
import com.kfyty.configuration.annotation.KfytyBootApplication;
/**
* 功能描述: 生成代碼主方法
*
* @author [email protected]
* @date 2019/9/07 21:58
* @since JDK 1.88
*/
@KfytyBootApplication
@EnableAutoGenerateSources(loadTemplate = false)
public class Main {
public static void main(String[] args) throws Exception {
KfytyApplication.run(Main.class);
}
}
- 看一下日誌:
12:11:31.859 [main] DEBUG com.kfyty.parser.ClassAnnotationParser - : found component: [class com.kfyty.demo.config.DataSourceConfig] !
12:11:31.886 [main] DEBUG com.kfyty.parser.ClassAnnotationParser - : found component: [class com.kfyty.demo.config.GenerateConfig] !
12:11:31.952 [main] DEBUG com.kfyty.parser.MethodAnnotationParser - : found bean resource: [interface javax.sql.DataSource] !
12:11:31.957 [main] DEBUG com.kfyty.parser.FieldAnnotationParser - : found auto wired: [interface javax.sql.DataSource] !
12:11:31.982 [main] DEBUG com.kfyty.configuration.ApplicationConfigurable - : auto configuration after check success !
12:11:32.480 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
12:11:33.230 [main] DEBUG com.kfyty.util.JdbcUtil - : ==> Preparing: select table_schema dataBaseName, table_name, table_comment from information_schema.tables where table_schema = ?
12:11:33.231 [main] DEBUG com.kfyty.util.JdbcUtil - : ==> Parameters: [test]
12:11:33.252 [main] DEBUG com.kfyty.util.JdbcUtil - : <== Total: 2 [class java.util.ArrayList]
12:11:33.280 [main] DEBUG com.kfyty.util.JdbcUtil - : ==> Preparing: select table_name, column_name field, data_type fieldType, if(column_key = 'PRI', 'true', 'false') primaryKey, if(is_nullable = 'YES', 'true', 'false') nullable, column_comment fieldComment from information_schema.COLUMNS where table_schema = ? and table_name = ?
12:11:33.280 [main] DEBUG com.kfyty.util.JdbcUtil - : ==> Parameters: [test, user]
12:11:33.284 [main] DEBUG com.kfyty.util.JdbcUtil - : <== Total: 3 [class java.util.ArrayList]
12:11:33.284 [main] DEBUG com.kfyty.generate.GenerateSources - : initialize data base info success !
12:11:33.310 [main] DEBUG com.kfyty.generate.GenerateSources - : generate resource:[User.java] success --> [D:\temp\code-generate\com\kfyty\User.java]
Process finished with exit code 0
- 查看生成的代碼:
package com.kfyty;
import java.util.Date;
import lombok.Data;
/**
* TABLE_NAME: user
* TABLE_COMMENT:
*
* @author kfyty
* @email [email protected]
*/
@Data
public class User {
/**
* 主鍵
*/
private Integer id;
/**
* 用戶名
*/
private String username;
/**
* 創建時間
*/
private Date createTime;
}
2、自定義 freemarker 模板生成
1、在 resource 文件夾下創建 template 文件夾
2、在 template 文件夾下創建 my_template1 文件夾。
因爲我對 freemarker 模板的命名是有規範的:
比如 dao.java.ftl,則代表生成的文件類型是 java,類名後綴是 Dao。
比如 entity_NoSu.java.ftl,代表生成的文件類型是 java,類名沒有後綴
比如 mapper.xml.ftl,代表生成的文件類型是 xml,文件名後綴爲 Mapper
所以,如果有多套模板的話,命名就會有衝突,因此需要多建一層文件夾,作爲模板前綴,以區分是哪一套模板
3、創建 entity_NoSu.java.ftl
package ${basePackage}
import java.util.Date;
import lombok.Data;
/**
* TABLE_NAME: ${table}
* TABLE_COMMENT: ${note}
*
* @author kfyty
* @email [email protected]
*/
@Data
public class ${className} {
protected ${pkField.fieldType} ${pkField.field};
<#list fields as field>
/**
* ${columns[field_index].field}: ${field.fieldComment}
*/
protected ${field.fieldType} ${field.field};
</#list>
}
4、在 resources 文件夾下創建配置文件 code-generate.properties
編寫的模板還需要在這裏配置才能被讀取;另外在這裏配置的變量,都可以在 freemarker 模板中直接使用
basePackage=com.kfyty
my_template1.template=\
entity_NoSu.java.ftl
5、相應的啓動類如下
package com.kfyty.demo;
import com.kfyty.KfytyApplication;
import com.kfyty.configuration.annotation.EnableAutoGenerateSources;
import com.kfyty.configuration.annotation.KfytyBootApplication;
/**
* 功能描述: 生成代碼主方法
*
* @author [email protected]
* @date 2019/9/07 21:58
* @since JDK 1.88
*/
@KfytyBootApplication
@EnableAutoGenerateSources(templatePrefix = "my_template1")
public class Main {
public static void main(String[] args) throws Exception {
KfytyApplication.run(Main.class);
}
}
6、生成的模板如下:
package com.kfyty
import java.util.Date;
import lombok.Data;
/**
* TABLE_NAME: user
* TABLE_COMMENT:
*
* @author kfyty
* @email [email protected]
*/
@Data
public class User {
protected Integer id;
/**
* username: 用戶名
*/
protected String username;
/**
* create_time: 創建時間
*/
protected Date createTime;
}
自定義生成模板
freemarker 模板本來就是高度自定義的,所以這裏只介紹基於 java 類的自定義模板。
1.1 自定義生成模板
上例中使用的是我編寫的默認的生成模板,如果不滿足需要的話,可以繼承 EntityTemplate 以自定義模板:
package com.kfyty.demo.config;
import com.kfyty.configuration.annotation.Component;
import com.kfyty.generate.info.AbstractDataBaseInfo;
import com.kfyty.generate.template.entity.EntityTemplate;
import com.kfyty.util.CommonUtil;
import java.io.IOException;
/**
* 添加自定義的 @Component 註解即可生效
*/
@Component
public class EntityTemplateConfig extends EntityTemplate {
@Override
public String classSuffix() {
return "Entity";
}
@Override
public String generateExtendsClass(AbstractDataBaseInfo dataBaseInfo) throws IOException {
return CommonUtil.fillString("AbstractEntity<{}, Integer>", this.className);
}
@Override
public String generateImplementsInterfaces(AbstractDataBaseInfo dataBaseInfo) throws IOException {
return "IEntity<Integer>";
}
}
看一下生成的代碼:
package com.kfyty.entity;
import java.util.Date;
import lombok.Data;
/**
* TABLE_NAME: user
* TABLE_COMMENT:
*
* @author kfyty
* @email [email protected]
*/
@Data
public class UserEntity extends AbstractEntity<UserEntity, Integer> implements IEntity<Integer> {
/**
* 主鍵
*/
private Integer id;
/**
* 用戶名
*/
private String username;
/**
* 創建時間
*/
private Date createTime;
}
1.2. 自定義表結構元數據:
1、編寫元數據承載實體類,比如我需要 jdbcType,那麼拓展的元數據實體類如下:
package com.kfyty.demo.config;
import com.kfyty.generate.info.AbstractTableInfo;
public class TableMeta extends AbstractTableInfo {
private String jdbcType;
}
2、編寫數據庫映射接口,自定義查詢 SQL:
package com.kfyty.demo.config;
import com.kfyty.configuration.annotation.Component;
import com.kfyty.generate.database.MySQLDataBaseMapper;
import com.kfyty.jdbc.annotation.Param;
import com.kfyty.jdbc.annotation.Query;
import java.util.List;
/**
* 添加自定義的 @Component 即可生效,同時需要把 GenerateConfig 中配置的 @DataBaseMapper 註解去掉
*/
@Component
public interface TableMetaConfig extends MySQLDataBaseMapper {
@Override
@Query("select data_type jdbcType, table_name, column_name field, data_type fieldType, if(column_key = 'PRI', 'true', 'false') primaryKey, if(is_nullable = 'YES', 'true', 'false') nullable, column_comment fieldComment from information_schema.COLUMNS where table_schema = #{dataBaseName} and table_name = #{tableName}")
List<TableMeta> findTableInfo(@Param("dataBaseName") String dataBaseName, @Param("tableName") String tableName);
}
這樣就可以在 java 模板中強轉爲 TableMeta 或者在 freemarker 中直接使用 jdbcType 啦。