字节码精简:优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码;
优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一;
自定义数组类型(FastStatementList)代替ArrayList:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描;
自定义集合类型(ConcurrentBag):提高并发读写的效率;
其他针对BoneCP缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究(但没说具体怎么优化)。
springboot版本是2.X,当使用spring-boot-starter-jdbc或者spring-boot-starter-data-jpa依赖,springboot就会自动引入HikariCP的依赖。
1)、概念:
以字段为依据,按照一定策略,将一个库中的数据拆分到多个库中。
2)、结果
每个库的结构都一样;数据都不一样;
所有库的并集是全量数据;
1)、概念
以字段为依据,按照一定策略,将一个表中的数据拆分到多个表中。
2)、结果
每个表的结构都一样;数据都不一样;
所有表的并集是全量数据;
1)、Sharding-JDBC直接封装JDBC API,旧代码迁移成本几乎为零。
2)、适用于任何基于Java的ORM框架,如Hibernate、Mybatis等 。
3)、可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等。
4)、以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖。
5)、分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。
6)、SQL解析功能完善,支持聚合、分组、排序、limit、or等查询。
spring:
messages:
encoding: utf-8
cache-seconds: 100000
profiles:
active: beta
/**
* 分库算法:编号后两位
*
* @author zhoudoujun01
* @date 2019年8月6日14:55:06
*/
public class DatabaseShardingAlgorithm implements ComplexKeysShardingAlgorithm {
public Collection doSharding(Collection availableTargetNames, Collection shardingValues) {
// 根据分片键名取得分片值列表
Collection orderIdValues = getShardingValue(shardingValues, Constant.COL_NAME_ID);
//
Collection dsList = new LinkedHashSet(availableTargetNames.size());
// 优先按照pid分库
if (orderIdValues != null && orderIdValues.size() > 0) {
for (String orderId : orderIdValues) {
String dsName = getDsNameById(orderId, availableTargetNames);
if (dsName != null && dsName.length() > 0) {
dsList.add(dsName);
}
}
}
if (dsList.size() == 0) {
throw new UnsupportedOperationException("分库失败(编号不符合规范)");
}
return dsList;
}
/**
* 根据编号取得数据源名:编号后两位
*
* @param id
* @param availableTargetNames
* @return 数据源名
*/
private String getDsNameById(String id, Collection availableTargetNames) {
String dsName = null;
for (String name : availableTargetNames) {
// 调用编号规则
if (name.endsWith(DataSourceId.getDataSourceID(id))) {
System.out.println("================== id: " + id + ", name: " + name);
dsName = name;
break;
}
}
return dsName;
}
/**
* 根据分片键名取得分片值列表
*
* @param shardingValues
* 分片值对象
* @param key
* 分片键名
* @return 分片值列表
*/
private Collection getShardingValue(Collection shardingValues, final String key) {
Collection valueSet = new ArrayList();
Iterator iterator = shardingValues.iterator();
while (iterator.hasNext()) {
ShardingValue next = iterator.next();
if (next instanceof ListShardingValue) {
@SuppressWarnings("unchecked")
ListShardingValue value = (ListShardingValue) next;
if (value.getColumnName().equals(key)) {
System.out.println("===================== key: " + key + ", values: " + value.getValues());
valueSet = value.getValues();
}
}
}
return valueSet;
}
}
/**
* 产生编号
*
* @return
*/
public static String buildID() {
long seqId = nextId();
String id = String.format(SEQID_FORMAT, seqId);
// 超过所占位数,截取后几位
if (id.length() > SEQID_DIGITS) {
id = id.substring(id.length() - SEQID_DIGITS, id.length());
}
// seqId后四位与数据源个数取模,得到数据源编号
int useridLast4 = (int) (seqId % ((int) Math.pow(10, USERID_LAST_DIGITS)));
int ds = useridLast4 % DATASOURCE_CNT;
// 时间
Calendar cal = Calendar.getInstance();
return String.format(ORDERNO_FORMAT, cal, id, ds);
}
test1.url=jdbc:mysql://localhost:3306/test1?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
test1.username=root
test1.password=root
test1.initialSize=5
test1.maxActive=5
test1.maxIdle=5
test1.minIdle=1
test1.maxWait=6000
test1.validationQuery=SELECT 1 FROM DUAL
test2.url=jdbc:mysql://localhost:3306/test2?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
test2.username=root
test2.password=root
test2.initialSize=5
test2.maxActive=5
test2.maxIdle=5
test2.minIdle=1
test2.maxWait=6000
test2.validationQuery=SELECT 1 FROM DUAL
数据源集成配置
@Bean
@Primary
public DataSource shardingDataSource() throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
// PERSON表分库规则
shardingRuleConfig.getTableRuleConfigs().add(getPersonRuleConfiguration());
return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, new HashMap(),
new Properties());
}
PERSON表分库规则
/**
* 定义Person表分库规则
* @return
*/
private TableRuleConfiguration getPersonRuleConfiguration() {
TableRuleConfiguration paymentRequestTableRule = new TableRuleConfiguration();
paymentRequestTableRule.setLogicTable(LOGIC_TABLE_PERSON);
paymentRequestTableRule.setActualDataNodes(ACTUAL_DATA_NODES_PERSON);
paymentRequestTableRule.setDatabaseShardingStrategyConfig(new ComplexShardingStrategyConfiguration(
SHARDING_COLUMN_ID, DatabaseShardingAlgorithm.class.getName()));
paymentRequestTableRule.setKeyGeneratorColumnName(KEY_GENERATOR_COLUMN_NAME_ID);
return paymentRequestTableRule;
}
组装数据源map
Prd 4个库
Beta 2个库
取系统环境beta
/**
* 系统环境名
*/
private static final String ACTIVE_ENV = "spring.profiles.active";
/**
* 多数据源别名列表
*/
private static String[] DS_NAMES = { "ds_00", "ds_01", "ds_02", "ds_03" };
/**
* 加载创建dataSource:
*
* @return
*/
private Map createDataSourceMap() {
HashMap dataSourceMap = Maps.newHashMap();
String activeEnvPrd = "prd";
// 得到系统环境
String activeEnv = env.getProperty(ACTIVE_ENV);
log.info("dataSource 1 url:{} activeEnv:" + activeEnv + ","
+ env.getProperty(MYSQL_DS_PREFIX + 1 + MYSQL_DS_SUFFIX_URL));
// 未配置或者指定生产环境时,直接使用指定数据源配置
if (activeEnv == null || activeEnvPrd.equalsIgnoreCase(activeEnv)) {
for (int i = 1; i <= DS_NAMES.length; i++) {
dataSourceMap.put(DS_NAMES[i - 1], getDataSourceItem(i));
}
}
// 测试环境下,数据源00,01对应1数据库,数据源02,03对应2数据库
else {
for (int i = 1; i <= DS_NAMES.length; i++) {
if (i <= 2) {
dataSourceMap.put(DS_NAMES[i - 1], getDataSourceItem(1));
} else if (i <= 4) {
dataSourceMap.put(DS_NAMES[i - 1], getDataSourceItem(2));
}
}
}
return dataSourceMap;
}
Hikari根据指定数据源配置标识生成数据源对象
private DataSource getDataSourceItem(int i) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(env.getProperty(MYSQL_DS_PREFIX + i + MYSQL_DS_SUFFIX_URL));
config.setUsername(env.getProperty(MYSQL_DS_PREFIX + i + ".username"));
config.setPassword(env.getProperty(MYSQL_DS_PREFIX + i + ".password"));
config.setMinimumIdle(Integer.parseInt(env.getProperty(MYSQL_DS_PREFIX + i + ".minIdle")));
config.setMaximumPoolSize(Integer.parseInt(env.getProperty(MYSQL_DS_PREFIX + i + ".maxIdle")));
config.setConnectionTimeout(Long.valueOf(env.getProperty(MYSQL_DS_PREFIX + i + ".maxWait")));
config.setConnectionTestQuery(env.getProperty(MYSQL_DS_PREFIX + i + ".validationQuery"));
config.setDriverClassName("com.mysql.jdbc.Driver");
HikariDataSource ds = new HikariDataSource(config);
return ds;
}
Configuration
@AutoConfigureAfter(ShardingDataSourceConfig.class)
public class SqlSessionFactoryConfig {
private static final Logger log = LoggerFactory.getLogger(SqlSessionFactoryConfig.class);
/**
* 扫描 Mapper 接口并容器管理
*/
static final String MAPPER_LOCATION_MYSQL = "classpath*:mapper/mysql/*.xml";
@Resource(name = "shardingDataSource")
private DataSource shardingDataSource;
@Bean(name = "transactionManager")
// @Primary
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(shardingDataSource);
}
@Bean(name = "mysqlSqlSessionFactory")
//@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("shardingDataSource") DataSource shardingDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(shardingDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(SqlSessionFactoryConfig.MAPPER_LOCATION_MYSQL));
return sessionFactory.getObject();
}
@Bean(name = "mysqlSqlSession")
// @ConditionalOnMissingBean
public SqlSessionTemplate mysqlSqlSessionTemplate(
@Qualifier(value = "mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.SIMPLE);
}
/**
* 分页插件
*/
// @Bean
// public PageHelper pageHelper(DataSource dataSource) {
// log.info("注册MyBatis分页插件PageHelper");
// PageHelper pageHelper = new PageHelper();
// Properties p = new Properties();
// p.setProperty("offsetAsPageNum", "true");
// p.setProperty("rowBoundsWithCount", "true");
// p.setProperty("reasonable", "true");
// pageHelper.setProperties(p);
// return pageHelper;
// }
}
@Configuration
@AutoConfigureAfter(SqlSessionFactoryConfig.class)
public class MyBatisMapperScannerConfig {
private Logger log = LoggerFactory.getLogger(MyBatisMapperScannerConfig.class);
private String notEmpty = "false";
private String identity = "MYSQL";
private String mysqlBasePackage = "com.adou.springboot.mybatis.mapper";
/**
* Mybatis扫描Mysql库相关的SQL文件的配置
*
* @return MapperScannerConfigurer对象
*/
@Bean(name = "mysqlMapperScanner")
public MapperScannerConfigurer mysqlMapperScannerConfigurer() {
log.info("mysql mapper 开始扫描" + mysqlBasePackage);
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("mysqlSqlSessionFactory");
mapperScannerConfigurer.setBasePackage(mysqlBasePackage);
Properties properties = new Properties();
properties.setProperty("notEmpty", notEmpty);
properties.setProperty("IDENTITY", identity);
mapperScannerConfigurer.setProperties(properties);
log.info("mysql mapper 加载完成");
return mapperScannerConfigurer;
}
}
@RestController
@RequestMapping("/person")
public class PersonController extends BaseController{
private static final Logger logger = LogManager.getLogger(PersonController.class);
@Autowired
private PersonService personService;
@PostMapping(value = "/selectById")
public ResponseDTO> selectById(String pid) {
logger.info("====================== pid: " + pid);
Person person = personService.selectById(pid);
ResponseDTO> response = onSuccess(person);
logger.info("====================== response: " + response);
return response;
}
}
/**
*
* 控制层基类
*
* @author wangzheng
*
*/
public abstract class BaseController {
/**
* 日志相关
*/
protected static Logger LOG = LoggerFactory.getLogger(BaseController.class);
/**
* 消息资源对象
*/
@Autowired
private MessageSource messageSource;
/**
* 成功时返回消息对象。
*
* @param data 业务消息体对象
* @param T 业务消息体类定义
* @return 应答结果对象
*/
public ResponseDTO onSuccess(T data) {
ResponseDTO out = new ResponseDTO();
out.setReturnCode(CommResultCode.SUCCESS);
out.setReturnMsg(messageSource.getMessage(CommResultCode.SUCCESS, null, LocaleContextHolder.getLocale()));
out.setResult(data);
return out;
}
/**
* 业务失败时返回错误失败对象。
*
* @param resultCode 结果码
* @return 应答结果对象
*/
public ResponseDTO> onFailedBiz(String resultCode) {
ResponseDTO> out = new ResponseDTO();
out.setReturnCode(resultCode);
out.setReturnMsg(messageSource.getMessage(resultCode, null, LocaleContextHolder.getLocale()));
out.setResult(null);
return out;
}
/**
* 业务失败时返回错误失败对象+data
*
* @param resultCode 结果码
* @param data 错误描述
* @return 应答结果对象
*/
public ResponseDTO onFailedBiz(String resultCode, String data) {
ResponseDTO out = new ResponseDTO();
out.setReturnCode(resultCode);
out.setReturnMsg(messageSource.getMessage(resultCode, null, LocaleContextHolder.getLocale()));
out.setResult(data);
return out;
}
/**
* 业务失败时返回错误失败对象。
*
* @param resultCode 结果码
* @param msgArgs 返回信息内置参数
* @return 应答结果对象
*/
public ResponseDTO> onFailedBiz(String resultCode, Object[] msgArgs) {
ResponseDTO> out = new ResponseDTO();
out.setReturnCode(resultCode);
out.setReturnMsg(messageSource.getMessage(resultCode, msgArgs, LocaleContextHolder.getLocale()));
out.setResult(null);
return out;
}
/**
* 时间类型的参数转化绑定
* @param binder 数据绑定器
*/
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new MyDateEditor());
}
/**
* 默认的时间转化类。支持以下格式的字符串转化为Date类。
* yyyy-MM-dd HH:mm:ss
* yyyy-MM-dd HH:mm
* yyyy-MM-dd
*
* @author wangzheng
*
*/
class MyDateEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
if (text == null || text.equals("")) {
setValue(date);
return;
}
try {
date = format.parse(text);
} catch (ParseException e) {
format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try {
date = format.parse(text);
} catch (ParseException e1) {
format = new SimpleDateFormat("yyyy-MM-dd");
try {
date = format.parse(text);
} catch (ParseException e2) {
throw new ParamCheckException("comm.constraints.AssertDate.message", text);
}
}
}
setValue(date);
}
}
}
@Service
public class PersonService {
@Autowired
private PersonMapper personMapper;
public Person selectById(String pid) {
return personMapper.selectPersonById(pid);
}
public int insert(Person person) {
return personMapper.insert(person);
}
}
@Mapper
public interface PersonMapper {
Person selectPersonById(String pid);
List selectAll();
int insert(Person person);
Long update(Person person);
Long delete(Long id);
}
INSERT INTO person(id,pid,name,age) VALUES(#{id},#{pid},#{name},#{age})
UPDATE person SET name=#{name},age=#{age} WHERE pid = #{pid}
DELETE FROM person WHERE pid = #{pid}
/**
* @author zhoudoujun01
* @date:2019年4月24日 下午4:45:08
* @Description:文本消息配置类。
*/
@Configuration
public class MessageConfiguration {
/**
* 消息文本资源
*/
@Autowired
private MessageSource messageSource;
/**
* 消息文本的缓存时间
*/
@Value("${spring.messages.cache-seconds}")
private long cacheMillis;
/**
* 消息文本编码
*/
@Value("${spring.messages.encoding}")
private String encoding;
/**
* 资源文件名称
*/
private static final String MESSAGES_VALIDATOR = "classpath:messages-validator";
/**
* 资源文件名称
*/
private static final String MESSAGES_RESULT = "classpath:messages-result";
/**
* 配置消息资源
* @return 消息资源对象
*/
@Primary
@Bean
public MessageSource messageResource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames(MESSAGES_VALIDATOR, MESSAGES_RESULT);
messageSource.setDefaultEncoding(encoding);
messageSource.setCacheMillis(cacheMillis);
messageSource.setUseCodeAsDefaultMessage(false);
return messageSource;
}
/**
* 设置默认区域解析器(中国)
* @return 区域解析器
*/
@Bean
public LocaleResolver localeResolver() {
FixedLocaleResolver slr = new FixedLocaleResolver();
// 默认区域
slr.setDefaultLocale(Locale.CHINA);
return slr;
}
/**
* 取得消息国际化访问器
*
* @return 消息国际化访问器
*/
@Bean
public MessageSourceAccessor messageSourceAccessor() {
// LocaleContextHolder.setDefaultLocale(Locale.US);
return new MessageSourceAccessor(messageSource, LocaleContextHolder.getLocale());
}
}
0000 = 成功
0001 = 服务器内部错误
0002 = 请求参数非法,错误信息为:{0}
0003 = ak不存在或者非法
springboot-mybatis