1.jooq是一个对象关系映射的轻量级框架,在sql执行方面的灵活性和对数据包装的严谨性方面都很优秀
2.jooq的配置
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.vendor.MySqlValidConnectionChecker; import java.util.Properties; import javax.sql.DataSource; import org.jooq.SQLDialect; import org.jooq.conf.RenderNameStyle; import org.jooq.conf.Settings; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; @Configuration @ConditionalOnProperty( value = {"jooq.enabled"}, matchIfMissing = true ) public class JooqConfiguration { private static final Logger logger = LogUtil.logger(JooqConfiguration.class); @Value("${jdbc.url}") private String url; @Value("${jdbc.driver}") private String driver; @Value("${jdbc.user}") private String user; @Value("${jdbc.password}") private String password; @Value("${jdbc.dialect:MYSQL}") private String dialect; @Autowired private Environment env; public JooqConfiguration() { } private String getMaxActive() { if (Tool.isNullOrEmpty(K8s.nameSpace())) { return this.env.getProperty("jdbc.dev.maxActive", "1"); } else { String str = Env.get("jdbc.maxActive"); if (Tool.isNullOrEmpty(str)) { str = this.env.getProperty("jdbc.maxActive"); } return Tool.isNullOrEmpty(str) ? "40" : str; } } @Bean( destroyMethod = "close" ) public DataSource dataSource() throws Exception { Properties properties = new Properties(); properties.put("url", Env.get("jdbc.url", this.url)); properties.put("driverClassName", Env.get("jdbc.driver", this.driver)); properties.put("username", Env.get("jdbc.user", this.user)); properties.put("password", Env.get("jdbc.password", this.password)); properties.put("initialSize", "1"); properties.put("maxActive", this.getMaxActive()); properties.put("maxWait", "10000"); properties.put("minIdle", "1"); properties.put("timeBetweenEvictionRunsMillis", "60000"); properties.put("minEvictableIdleTimeMillis", "300000"); properties.put("validationQuery", "SELECT 1"); properties.put("testWhileIdle", "true"); properties.put("testOnBorrow", "false"); properties.put("testOnReturn", "false"); properties.put("maxOpenPreparedStatements", "10"); properties.put("removeAbandoned", "true"); properties.put("removeAbandonedTimeout", "1800"); properties.put("logAbandoned", "true"); logger.info("data base connection info: " + properties); DruidDataSource dds = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties); dds.setFilters("log4j"); properties = new Properties(); properties.put("druid.stat.slowSqlMillis", "3000"); properties.put("druid.stat.logSlowSql", "true"); properties.put("druid.stat.mergeSql", "true"); dds.setConnectProperties(properties); dds.setValidConnectionChecker(new MySqlValidConnectionChecker()); return dds; } @Bean public DataSourceConnectionProvider connectionProvider(DataSource dataSource) throws Exception { return new DataSourceConnectionProvider(new TransactionAwareDataSourceProxy(dataSource)); } @Bean public DefaultConfiguration configuration(DataSourceConnectionProvider connectionProvider) throws Exception { DefaultConfiguration jooqConfiguration = new DefaultConfiguration(); jooqConfiguration.set(connectionProvider); SQLDialect dia = SQLDialect.valueOf(this.dialect); jooqConfiguration.setSQLDialect(dia); Settings settings = (new Settings()).withExecuteLogging(true).withRenderFormatted(true); if (dia == SQLDialect.H2) { settings.withRenderSchema(false).withRenderNameStyle(RenderNameStyle.AS_IS); } jooqConfiguration.set(settings); return jooqConfiguration; } }
3.配置yml
server: port: 8085 servlet: context-path: /customer jdbc: url: jdbc:mysql://113.140.12.194:13306/customer_tuiguang?useSSL=false datasource: customer_tuiguang driver: com.mysql.cj.jdbc.Driver user: chdsd password: Chdsd20# dialect: MYSQL dev: token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0b2dlZWsiLCJuYW1lIjoi5bGx5Lic566h55CG5ZGYIiwiaWQiOiJjaGRzZGFkbWluIn0.FmzO8352eVHKLkNnXx_eT6IgFeZmA2mZ6wxuZaWRxKR3jTT8WUgagjbvVbCcS6O5uAaBFrocXezudr00L6bQmi0KLm3dG7yOrj7RoWpn-dS1o8KaDn-z7MppTBaRBflROD42Mz9XjJNQGWcajrheJsCuAGW8EAgsR3KSrLvqqHDYooLXO8xL7imETx6OcMtv_wHJjauIkWKNyfVsgVzXBuUpZN7ORMZKYEp7DXjGHBPxPCPoDpZaVqYYUjWb98sNmSW2SpgfF3SQMC43W8yVbK8BcFQ9iwHzXiec1Wb0li_U-kJ5wXecDDnARvovVV3nx6Vr5ydtagsq0be-snlcFQ #service: # host: http://113.140.12.194:18083/chd-dev/api # url-contract: https://www.chdes.com.cn/chd-dev/api # baidu-ak: Y9CacQfm6ZRFfKW7FOjia5lZdyjNvtBa #dev: # token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0b2dlZWsiLCJuYW1lIjoi5bGx5Lic566h55CG5ZGYIiwiaWQiOiJjaGRzZGFkbWluIn0.FmzO8352eVHKLkNnXx_eT6IgFeZmA2mZ6wxuZaWRxKR3jTT8WUgagjbvVbCcS6O5uAaBFrocXezudr00L6bQmi0KLm3dG7yOrj7RoWpn-dS1o8KaDn-z7MppTBaRBflROD42Mz9XjJNQGWcajrheJsCuAGW8EAgsR3KSrLvqqHDYooLXO8xL7imETx6OcMtv_wHJjauIkWKNyfVsgVzXBuUpZN7ORMZKYEp7DXjGHBPxPCPoDpZaVqYYUjWb98sNmSW2SpgfF3SQMC43W8yVbK8BcFQ9iwHzXiec1Wb0li_U-kJ5wXecDDnARvovVV3nx6Vr5ydtagsq0be-snlcFQ service: # host: http://113.140.12.194:18083/chd/api activiti: host: http://localhost:8080/activiti authentication: host: http://localhost:8082/authentication # host: http://localhost:8080 baidu-ak: Y9CacQfm6ZRFfKW7FOjia5lZdyjNvtBa spring: servlet: multipart: max-file-size: 30MB max-request-size: 30MB liquibase: changelog: classpath:/db/changelog/db.changelog-master.xml data: mongodb: host: 39.96.6.37 port: 27027 database: dev-attachment username: tuji password: tuji2018
4.自己封装的Dbvisitor
import lombok.AllArgsConstructor; import lombok.Data; import org.jooq.DSLContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import javax.sql.DataSource; @Configuration @Component public class DBvisitor { public static DSLContext dsl; public static JdbcTemplate jdbcTemplate; @Autowired private DSLContext dslContext; @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } @Autowired public void setTemplate(JdbcTemplate template){ DBvisitor.jdbcTemplate = template; } @Autowired public void setDsl() { DBvisitor.dsl = dslContext; } }
5.gradle配置
buildscript { repositories { mavenCentral() maven { url "${nexusUrl}/content/groups/public/" } } dependencies { classpath group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.0.RELEASE") classpath("cn.togeek.plugin:jooq:1.0.0-SNAPSHOT") classpath("cn.togeek.plugin:liquibase:1.0.0-SNAPSHOT") } } apply plugin: 'java' apply plugin: 'maven' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'cn.togeek.plugin.jooq' apply plugin: 'cn.togeek.plugin.liquibase' bootJar { baseName = 'customer' version = '2.0.0-SNAPSHOT' } repositories { mavenCentral() maven { url "${nexusUrl}/content/groups/public/" } } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { // compile("cn.togeek.power-base:customer-base:chd-tuiguang-SNAPSHOT") compile group: 'org.liquibase', name: 'liquibase-core', version: '3.6.1' compile project(':customer-base') compile "axis:axis:1.4" compile "javax.ejb:ejb-api:3.0" compile "org.jxls:jxls:2.4.3" compile "org.jxls:jxls-jexcel:1.0.6" compile "org.jxls:jxls-poi:1.0.14" compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0' compile group: 'org.freemarker', name: 'freemarker', version: '2.3.23' compile group: 'commons-httpclient', name: 'commons-httpclient', version: '3.1' compile group: 'org.springframework', name: 'spring-core', version: '5.0.20.RELEASE' compile 'cn.hutool:hutool-all:4.0.8' compile group: 'org.liquibase', name: 'liquibase-core', version: '3.6.1' runtime group: 'mysql', name: 'mysql-connector-java', version: '6.0.6' compile 'cn.afterturn:easypoi-base:3.0.3' compile 'cn.afterturn:easypoi-web:3.0.3' compile 'cn.afterturn:easypoi-annotation:3.0.3' compile 'org.apache.pdfbox:pdfbox:2.0.4' //compile 'org.apache.poi:ooxml-schemas:1.1' compile group: 'com.gexin.platform', name: 'gexin-rp-sdk-http', version: '4.1.0.5' compile fileTree(dir: 'libs', include: '*.jar') testCompile group: 'com.h2database', name: 'h2', version: '1.4.197' compile ('org.apache.poi:poi-ooxml:4.1.2'){ exclude module: 'xmlbeans' } compile 'org.apache.xmlbeans:xmlbeans:3.1.0' } configurations { all*.exclude module: 'spring-boot-starter-logging' all*.exclude module: 'logback-core' all*.exclude module: 'logback-classic' } task buildTask(type: Jar, dependsOn: bootJar) { doLast { copy { from 'k8s' into 'build/k8s' } copy { from 'docker' into 'build/docker' } copy { from 'build/libs' into 'build/docker' include '*.jar' } copy { from 'src/main/resources/font' into 'build/docker/font' } } } task run(dependsOn: bootJar) << { javaexec { main="-jar"; args bootJar.archivePath } } compileJava { options.encoding = "UTF-8" }
7.setting.gradle
include ':customer-base'
project(':customer-base').projectDir=file('D:\\ideaWorkspace\\customer-base')
8.定时任务中使用Dbvisitor
import lombok.AllArgsConstructor; import lombok.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static cn.togeek.jooq.Tables.*; @Configuration @EnableScheduling public class TaskPool { private final long MILLISECONDS = 1; private final long SECONDS = 1000; private final long MINUTES = 60 * SECONDS; private final long HOURS = 60 * MINUTES; private final long DAYS = 24 * HOURS; private final Logger logger = LoggerFactory.getLogger(TaskPool.class); private String METHOD = ""; @Autowired private DBvisitor DBvisitor; @Scheduled(fixedRate= 1 * HOURS) private void configureTasks() throws ExecutionException, InterruptedException { syncTsorganizationTrorganizationTorganizationIndb(); } /** * 客户数据维护 * 维护auth.torganization,customer.trorganization,customer.tsorganization数据 * 目标:定期扫描customer.tsorganization数据,维护到auth.torganization和trorganization,customer中 * 解决:多种客户来源方式,包括但不限于客户导入,客户新增,微信+网厅新增关联企业过程微服务调用链路某个节点异常导致的数据缺失问题 */ private void syncTsorganizationTrorganizationTorganizationIndb() throws ExecutionException, InterruptedException { List<TSOrganizationRecord> records = DBvisitor.dsl.selectFrom(T_S_ORGANIZATION).where(T_S_ORGANIZATION.TYPE.eq(OrgType.DLYH.getValue())).fetchInto(TSOrganizationRecord.class); List<TROrganizationRecord> mappings = DBvisitor.dsl.selectFrom(T_R_ORGANIZATION).fetchInto(TROrganizationRecord.class); List<record> customersInAuth = (List<record>) Rest.get(ServiceUrl.AUTH.concat("/organizations/").concat(OrgType.DLYH.getValue()+"/").concat("all"), List.class).get().stream().map(x -> { return new record((String)(((Map) x).get("id")),(String)(((Map) x).get("name"))); }).collect(Collectors.toList()); List<String> tsIdMappings = mappings.stream().map(TROrganizationRecord::getOrganizationId).distinct().collect(Collectors.toList()); List<TROrganizationRecord> missingMappings = new CopyOnWriteArrayList<>(); //映射缺省的数据 List<record> missingTorgnizations = new CopyOnWriteArrayList<>(); //auth库缺省的数据 List<String> customersName = customersInAuth.stream().map(record::getValue).distinct().collect(Collectors.toList()); records.stream().forEach(record -> { String sId = record.getId(); String name = record.getName(); if (!customersName.contains(name)) { //auth 的客户库里没数据 String uuid = UUIDUtil.getUUID(); missingTorgnizations.add(new record(sId, uuid)); if (!tsIdMappings.contains(sId)) { //且customer无映射关系 missingMappings.add(buildTrorganization(record, uuid)); } } else { // auth 的客户库里有数据 if (!tsIdMappings.contains(sId)) { //但customer无映射关系 TaskPool.record data = customersInAuth.get(customersName.indexOf(name)); missingMappings.add(buildTrorganization(record, data.getKey())); } } }); String authSql = getSql(records,missingTorgnizations); if (!authSql.endsWith("VALUES;")) { logger.info("要操作的auth库sql:"+authSql); int row = DBvisitor.jdbcTemplate.update(authSql); logger.info("auth库插入了{}条数据",row); } if (!missingMappings.isEmpty()) { logger.info("要维护的customer.trorganiztion库的数据个数:"+missingMappings.size()); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("INSERT INTO `customer_tuiguang`.`t_r_organization`(`id`, `organization_id`) VALUES ("); missingMappings.stream().forEach(x-> { stringBuilder.append("'"+x.getId()+"',").append(""+x.getOrganizationId()+"'),("); }); String mappingSql = stringBuilder.toString(); mappingSql = mappingSql.substring(0,mappingSql.length()-2); int mappingInsert = DBvisitor.jdbcTemplate.update(mappingSql); // int length = dsl.batchInsert(missingMappings).execute().length; 插入太耗时 // logger.info("customer.trorganiztion插入{}条数据",length); logger.info("customer.trorganiztion插入{}条数据",mappingInsert); } } private TROrganizationRecord buildTrorganization(TSOrganizationRecord record,String tId) { TROrganizationRecord organizationRecord = new TROrganizationRecord(); BeanUtils.copyProperties(record,organizationRecord); organizationRecord.setId(tId); organizationRecord.setOrganizationId(record.getId()); organizationRecord.setPkId(null); return organizationRecord; } private String getSql(List<TSOrganizationRecord> records,List<record> missingTorgnizations){ StringBuilder sqlbuilder = new StringBuilder(); /** * INSERT INTO `authentication_tuiguang`.`t_organization`(`pk`, `id`, `name`, `alias`, `status`, `type`, `parent_id`, `create_time`, `last_modified_time`, `province_code`, `city_code`, `county_code`, `audit_status`, `applicant`, `audit_reason`, `order`, `power_source`) VALUES (490, '7830A3EC-9610-4260-B29D-35935FA23613-00820', '山东新和成维生素有限公司', NULL, 1, 256, NULL, '2019-01-11 20:16:34', '2019-01-11 20:16:34', NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL); */ List<String> keys = missingTorgnizations.stream().map(record::getKey).collect(Collectors.toList()); List<String> values = missingTorgnizations.stream().map(record::getValue).collect(Collectors.toList()); sqlbuilder.append("INSERT INTO `authentication_tuiguang`.`t_organization`(`id`, `name`, `status`, `type`, `create_time`, `last_modified_time`, `order`) VALUES ("); records.stream().filter(x -> keys.contains(x.getId())).forEach(s -> { try { sqlbuilder.append(Deal(values.get(keys.indexOf(s.getId())))).append(",") .append(Deal(s.getName())).append(",") .append(1).append(",") .append(OrgType.DLYH.getValue()).append(",") .append(Deal(s.getCreateTime().toString())).append(",") .append(Deal(s.getLastModifiedTime().toString())).append(",") .append(0).append("),("); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }); String sql = sqlbuilder.toString(); sql = sql.substring(0, sql.length()-2).concat(";"); return sql; } private Object Deal(Object obj) throws InstantiationException, IllegalAccessException { if (obj == null) { if ((obj instanceof String)) { return "''"; } else { return null; } } else { if ((obj instanceof String)) { return "'"+ obj.toString() +"'"; } else { return obj.toString(); } } } @Data @AllArgsConstructor public class record { private String key; private String value; } }