开篇
写在前面的话,每一篇摘文都以实际案例场景出发,空余时间记录每一次mark历程,在不一样的业务实际场景下,针对项目阶段所产生的变化,制定不一样的技术方案,不论多么渺小的技术方案,放在其对应的场景下都有着不一样的意义。实践是检验真理的唯一标准,当真正实操过后参与讨论,或许会让你有一点新发现,希望对读者在思考上有点不一样的IDea,欢迎Join一起交流探讨,热衷拥抱新知识,旨在技术交流+心得分享->每天译点晓知识。
简介
背景
思考
场景
达梦适配过程
Q-A NO.1
接下来,打开DM8客户端,可通过dm sql脚本方式去创建表,这里只是简单创建了一张crm_version表。这里尤其需要注意的是创建表名不需要带双引号,达梦默认是大写,sql方言中也不需要额外处理,若是通过DM8工具去建表建字段或者带小写加双引号创建脚本,出现双引号则在实际的sql方言中也需要加上双引号,否则执行sql会抛出视图或表不存在,字段列名不存在的异常。
若是通过Mysql或Oracle或其他数据库,文件等方式迁移导入。这里记录一下迁移过程中遇到的问题,在迁移的时候,报某些字段超长。于是,查看了MySql中那些字段的类型及长度,都是varchar(50) 。这里应该是迁移有些字段,须在DM数据库中增加位宽,在MySql中varchar是表示字符,varchar(50)表示可以存放50个字符,但是DM的默认跟Oracle是一样的,varchar(50)表示50个字节。这就意味着,50个字节,如果存中文,在utf-8的字符集下,只能存最多16个。所以,如果MySql库到DM,varchar类型,需特别留意一下。 在项目工程中引入达梦数据库驱动,SpringBoot对MySql做了集成,没有get到对达梦数据库做集成,小编这里采用的jdk1.8,安装的达梦数据库也是DM8,所以这里引入: DmJdbcDriver18,其相对于DmJdbcDriver17作出了很大的改进。
i、本地引入的方式 在pom.xml文件中,引入依赖jar
<!-- 达梦数据库驱动 -->
<dependency>
<groupId>com.dm</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/DmJdbcDriver18.jar</systemPath>
</dependency>
ii、nexus私服引入的方式
在pom.xml文件中,引入依赖jar
<!-- 达梦数据库驱动 -->
<dependency>
<groupId>com.dm</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>1.8</version>
</dependency>
说明:这里的groupId坐标参数,可由使用者自行在nexus中upload创建声明,然后在pom.xml中引入相关坐标即可。
扩展:若是需要从本地deploy到nexus或是先获取本地仓库.m2的包->nexus仓库的包->aliyun maven仓库的包,为了解决开发过程中jar包拉取异常等问题,则在maven中settings.xml中可这样去配置:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 本地仓库 -->
<localRepository>D:/.m2</localRepository>
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<name>yd nexus</name>
<url>http://ip:port/repository/maven-public/</url>
</mirror>
</mirrors>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>maven-snapshots</id>
<url>http://ip:port/repository/maven-snapshots/</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>maven-releases</id>
<url>http://ip:port/repository/maven-releases/</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
</profile>
</profiles>
<servers>
<server>
<id>nexus</id>
<username>yxd179</username>
<password>yxd179</password>
</server>
<server>
<id>maven-releases</id>
<username>yxd179</username>
<password>yxd179</password>
</server>
<server>
<id>maven-snapshots</id>
<username>yxd179</username>
<password>yxd179</password>
</server>
</servers>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>
说明:这里采取Nacos注册中心去管理项目工程中用到的一些配置信息,Nacos的介绍暂不详细说明,欢迎一起讨论交流,至此,达梦数据库环境安装,相关版本及其依赖的选取跟引入,配置信息完毕。
Q-A NO.2
// 驱动-连接地址-账号-密码等信息
String driverClassName = "dm.jdbc.driver.DmDriver";
String url = "jdbc:dm://localhost:5236/";
String username = "yxd179";
String password = "yxd179";
// 加载驱动
Class.forName(driverClassName);
// 获取数据库连接对象
Connection con = (Connection) DriverManager.getConnection(url,username,password);
// 获取数据库操作对象
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM TEST;");
// 执行sql
ResultSet rs= ps.executeQuery();
// 这里还可以获取到数据库产品名称
DatabaseMetaData metaData = (DatabaseMetaData) con.getMetaData();
// 这里为后续提到的在xml指定达梦的databaseId奠定基础
System.out.println("数据库产品名称:" + metaData.getDatabaseProductName());
最后需关闭连接close,释放资源->rs-ps-con.
tk.mybatis:mybatis定制的第一大业务增强库。
pagehelper:分页控件,mybatis定制的第二大业务增强库。
Q-A NO.3
其实,这都是需要我们care到的。当mybatis装配时,若是同一个方法被找到多条sql时,首先,会优先使用 databaseId 相同的 sql。若是没有 databaseId 相同的sql,其次,再使用未配置 databaseId 的 sql,而databaseId 未对应的 sql 不会使用。
i、当获取到的数据源信息为mysql,则执行图一中批量插入insertBatch方法;
ii、当获取到的数据源信息为db2,则会执行图二中批量插入insertBatch方法;
iii、当获取到的数据源信息为oracle,则会执行图三批量插入insertBatch方法。
上例,这样我们就能极其简易的指定 databaseId,很多小伙伴肯定会说为什么需要这样去指定?其背后的原理又是怎样的,我们是否能够扩展并自定义 databaseId?框架这层的应用真能够提供的这么 perfect 吗?
在上一个Q-A中,我们已经get到了数据库产品的名称,可以从数据源连接对象中去获取,不妨从这里出发。这里先提出一点 little 猜想,mybatis既然能够支持mysql,oracle,db2等等数据库,那么其他关系型数据库?肯定是提供一些这样的入口可以去扩展的,只是各种框架的适配程度不一样,都在不断兼容。网上关于这块的资料并不全面,基于数据库产品名称这条线索,于是,小编封装了独立的适配器sdk,可作达梦等关系型数据库适配。当然不同类型的数据库,后续在sdk中去扩展兼容都是可以做到的。
说明:有兴趣的童鞋欢迎一起讨论交流,welcome together^_^
i、通过配置文件属性方式指定databaseId:
mybatis:
mapper-locations: classpath*:mapper/**.xml
configuration:
database-id: dm8
ii、 通过configuration配置类,往容器注入Bean方式指定databaseId:
/**
* @Auther: X.D.Yang
* @Date: 2021/4/1 13:14
* @Description:
*/
@Configuration
public class DatabasesConfig {
private static final Logger logger = LoggerFactory.getLogger(DatabasesConfig.class);
@Bean
public DatabaseIdProvider getDatabaseIdProvider() {
DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties p = new Properties();
logger.info("Join DM8 databaseId Starting...");
p.setProperty("DM DBMS", "dm8");
//p.setProperty("MySQL", "mysql");
databaseIdProvider.setProperties(p);
logger.info("Join DM8 databaseId Start completed.");
return databaseIdProvider;
}
}
iii、mybatis-config.xml配置方式指定databaseId:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="DM DBMS" value="dm8" />
</databaseIdProvider>
至于封装的独立sdk,其主要思想结合SpringBoot-自动装配-条件配置:
@SpringBootApplication->@SpringBootConfiguration(@Configuration注解,声明为spring的配置类)、@EnableAutoConfigurationspringboot(启动最关键的注解)、@ComponentScan(对包进行扫描)
1、其中@EnableAutoConfiguration注解会读取所有classpath:META-INF/Spring.factories,取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration下的所有value,注册到核心容器,完成自动配置类的加载。
读取:spring提供的工具类-SpringFactoriesLoader>>>第一个参数是个Class对象,决定读取的key,为class对象的全类名。第二个参数是ClassLoader对象,决定从哪里开始找这个文件,然后读取classpath:META-INF/spring.factories文件。
2、自动配置类上有大量自动配置生效的条件,比如依赖是否被引入。springboot采用了默认代替配置的策略,当然也可更改默认配置,比如修改application.yml>yamlproperties配置文件,手动往容器中注册特定bean,注册一些实现特殊接口的类,比如实现WebMvcConfigurer接口的类。自动配置即向容器注册组件,实现了特殊接口的组件,影响spring的生命周期。
/**
* @Auther: X.D.Yang
* @Date: 2021/3/15 20:39
* @Description:
*/
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoggedInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
@Bean
public LoggedInterceptor getLoggedInterceptor() {
return new LoggedInterceptor();
}
}
//WebMvcConfigurer接口还有很多实现方法
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public WebMvcConfigurerAdapter() {
}
public void addInterceptors(InterceptorRegistry registry) {
}
}
其中,
@ConditionalOnClass:一些类是否存在当前类路径下。
@ConditionalOnProperty:相应的配置字段满足。
@ConditionalOnMissingBean:容器中没有某个bean。
这里我们着重看一下,mybatis提供的自动配置包-MybatisAutoConfiguration,生效条件:需引入mybatis相关的依赖,核心容器中只有一个DataSource,在DataSource自动配置完成后才生效等等条件,并且还开启了配置类@EnableConfigurationProperties(MybatisProperties.class)
上述构造方法:当前对象被实例化时,会被注入一系列的bean-MybatisProperties对象...
3、mybatis为我们注册了SqlSessionFactory,SqlSessionTemplate,以及为每个@Mapper注册了一个Mapper实现类-MyBatis->@Autowired注入一个Mapper。
当我们自己为容器中注入SqlSessionFactory对象,从容器中取dataSource(当引入spring-boot-starter-jdbc时会自动配置)作为参数,创建一个SqlSessionFactoryBean对象,该对象是个工厂-生产SqlSessionFactory,这里通过SqlSessionFactoryBean对象的一系列set方法,最后调用getObject方法来获取到SqlSessionFactory对象。其中生成SqlSessionFactory需要的Configuration对象,这里也可以取代mybatis的主配置文件-sqlSessionFactoryBean.setConfiguration方法。
而SqlSessionTemplate的创建,则取上述注入的SqlSessionFactory,new SqlSessionTemplate对象即可。至于对MyBatis源码Debug有兴趣的可以参考小编之前的文章>对Mybatis源码的认识_yxd179的博客-CSDN博客
记录:在适配的过程中还试出了达梦的幻读,达梦和Oracle一样默认的隔离级别都是读提交;还有特别需要注意的就是,在数据库中varchar类型的字段,比如在mysql中定义varchar(50),在写满的情况下为50个字符,到了达梦这边最多也就是varchar(150),当然可自行指定位宽,小编用的字符集是UTF-8,每个字符占三个字节,乘以三倍,就肯定是装得下的。目前达梦适配现有的框架为: HiKari+MyBatis3.4.6+tk.MyBatis+PageHelper
ok,now,接入sdk适配后,启动的效果,大致如下: 这里,主要对sql分页查询作下阐述:简单的分页可通过手写sql limit...,首先统计count总数,然后计算分页等相关信息,这里不着重说明,当然sql分页控件也是可以去扩展的。PageHelper分页,其主要原理:首先将传递的参数设置到page这个对象中去,然后会接着将page的副本存放入ThreadLoacl中,保证分页时参数互不影响,通过mybatis提供的拦截器,获取ThreadLocal中的值,重新拼装分页sql语句。
接入之后,我们在控制台输出dm sql日志,看一下打印的sql-伪列分页:
在DM8中正常执行sql,获取结果集:
而在MySql中,简单limit分页输出是这样的:
<附注>Debug分页控件源码,其实也很容易理解,分页最终是这样去拼装的:
protected BoundSql getPageBoundSql(Object parameterObject) {
String tempSql = sql;
String orderBy = PageHelper.getOrderBy();
if (orderBy != null) {
tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
}
tempSql = localParser.get().getPageSql(tempSql);
return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
}
//当然,这里有很多其他的Parser,这里可扩展,可兼容,去适用于不同的数据库
public String getPageSql(String sql) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120);
sqlBuilder.append("select * from ( select tmp_page.*, rownum row_id from ( ");
sqlBuilder.append(sql);
sqlBuilder.append(" ) tmp_page where rownum <= ? ) where row_id > ?");
return sqlBuilder.toString();
}
Q-A NO.4
结尾
末尾:后续Java框架体系,数据库技术体系,大数据体系进阶案例实战都会同时更新,微信公众号同步,旨在分享的初衷,欢迎提出宝贵建议^_^