# bee
**Repository Path**: melin/bee
## Basic Information
- **Project Name**: bee
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 5
- **Forks**: 4
- **Created**: 2020-05-25
- **Last Updated**: 2026-02-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
基于Spring + Hibernate + JdbcTemplate + Jinja 封装的DAO层,简单的CURD使用Hibernate,join等复杂sql可使用freemarker 组装sql,可以省去大半的sql,方便简单快捷。
Base Service和Dao封装了大量的方法,可以省去了大量的代码,基本CURD操作基本需要自定方法,datacompute 项目基于bee开发dao层代码。详细内容请参考:
- [BaseServiceImpl.java](https://gitee.com/melin/bee/blob/master/src/main/java/com/gitee/bee/core/service/BaseServiceImpl.java)
- [HibernateBaseDaoImpl.java](https://gitee.com/melin/bee/blob/master/src/main/java/com/gitee/bee/core/hibernate5/HibernateBaseDaoImpl.java)
引用:所有依靠代码生成的应用都是DD,动态字节码才是王道。基于mybatis写的项目终将变成难以维护。所有把面向对象变成面相过程的设计规范、框架都是技术发展的倒退
```xml
com.gitee.melin
bee
2.0.0-SNAPSHOT
```
> 2.x 版本最低支持jdk17
### Build
```
export GPG_TTY=$(tty)
mvn clean spotless:apply deploy -Pdeploy
```
### 一、使用步骤,以用户实体为离
1. 定义User Entity,继承BaseEntity
```java
@Entity
@Table(name="TEST_USER")
public class User extends BaseEntity {
private String name;
private int age;
// 省略getter 和 setter
}
```
2. 定义DAO类,定义接口实际意义不大,不推荐定义接口
```java
@Repository
public class UserDao extends HibernateBaseDaoImpl {
}
```
3. 定义Service类
```java
@Service
public class UserService extends BaseServiceImpl {
@Autowired
private UserDao userDao;
@Override
public HibernateBaseDao getHibernateBaseDao() {
return userDao;
}
}
```
4. 参考测试用例:PaginationDaoTest
### 二、ActiveRecord 模式实现
在Rails 和 Grails 有比较成熟的 ActiveRecord 模式应用,简单尝试中,后续开发继续完善
```
1、初始化spring bean 激活 ActiveRecord
2、Entity 集成 ActiveRecord, ActiveRecord 集成 BaseEntity
@Entity
@Table(name="TEST_ACCOUNT")
@Data
public class Account extends ActiveRecord {
...
}
3、参考第一节,定义Dao和Service
4、example
@Test
public void testFindByID() {
Account account = new Account();
account.setName("melin");
account.setRole(RoleEnum.ADMIN);
Long id = account.save();
Account _account = Account.findById(id);
assertEquals("melin", _account.getName());
assertEquals(RoleEnum.ADMIN, _account.getRole());
}
```
### 三、对于复杂的sql,建议使用Spring JdbcTemplate,为了避免SQL直接写在代码中 ,推荐写在配置文件中,具体使用方法:
1. 在resources目录下创建目录custom-sql,custom-sql 目录中创建xml文件,xml文件定义sql语句,一个xml文件可以定义多个sql语句,建议一个DAO类对应一个xml文件,例如:user-sql.xml
```xml
```
2. CustomSQL 注册为spring bean
3. 代码中使用 CustomSQLUtil.get(String id, Map models) 获取sql。
### 五、Entity 字段定义enum,解决spring mvc 和 hibernate 支持enum,Example:
spring mvc Formatter 支持 Enum
```java
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new IntegerToEnumConverterFactory());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
```
hibernate
```java
@JsonSerialize(using = JacksonEnumStringSerializer.class)
// @JsonSerialize(using = JacksonEnumIntegerSerializer.class)
public enum RoleEnum implements BaseStringEnum { //如果enum value为int 请使用 BaseIntegerEnum
ADMIN("admin", "管理员"),
USER("user", "普通用户");
private String value;
private String desc;
RoleEnum(String value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
@JsonValue
public String getValue() {
return value;
}
public String getDesc() {
return desc;
}
}
@Entity
@Table(name="TEST_ACCOUNT")
@Data
public class Account {
@Id
@GeneratedValue(generator = "tableGenerator")
@GenericGenerator(name = "tableGenerator", strategy="increment")
private Long id;
private String name;
private String email;
private int age;
private String cardNo;
@Column(name = "role")
@Type(type = "com.gitee.melin.bee.core.enums.StringValuedEnumType", parameters = {
@Parameter(name = "enumClass", value = "com.gitee.melin.bee.core.enums.RoleEnum")})
private RoleEnum role;
@OneToOne(optional = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "address_id")
private Address address;
}
@Test
public void testFindByID() {
Account account = new Account();
account.setName("melin");
account.setRole(RoleEnum.ADMIN);
Long id = accountDao.save(account);
Account _account = accountDao.get(id);
assertEquals("melin", _account.getName());
assertEquals(RoleEnum.ADMIN, _account.getRole());
}
```
spring jdbcTemplate 查询解析枚举字段
```java
@Bean
public DefaultConversionService conversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverterFactory(new IntegerToEnumConverterFactory());
conversionService.addConverterFactory(new StringToEnumConverterFactory());
return conversionService;
}
public List getJobsByDependentCode(String dependentCode,
String scheduleStart, JobInstanceType jobInstanceType) {
Map param = new HashedMap();
param.put("dependentCode", dependentCode);
param.put("scheduleStart", scheduleStart);
param.put("type", jobInstanceType.getValue());
String sql = CustomSQL.getInstance().get("jobInstance.getJobsByDependentCode", param);
BeanPropertyRowMapper rowMapper = BeanPropertyRowMapper.newInstance(JobInstanceEntity.class, conversionService);
return namedParameterJdbcTemplate.query(sql, param, rowMapper);
}
```
### 六、参数参数key 定义
一个系统中会存在很多动态参数定义,一般直接使用一个字符串作为参数key,例如:getString("xxx.xxx")。参数定义很多以后,随着系统不断迭代,会存在很多问题,例如不确定系统有多少个配置参数,参数默认值可能不统一,参数不确定从哪个版本引入,参数是否不再需要,参数使用备注等等问题。
借鉴Spark 代码中参数定义设计:
```java
public class DataWorkerConf extends BeeConf {
//---------------------------------------------------------------------------------------------------------------
public static final ConfigEntry DATAWORK_DATA_CENTER = buildConf("datawork.data.center")
.doc("指定 Dataworker 数据中心,例如在杭州,上海都有数据中心,可以指定值为:shanghai, hangzhou")
.version("3.0.0")
.stringArrayConf()
.createWithDefault(StringArray.of("hangzhou"));
public static final ConfigEntry DATAWORK_DATA_CENTER_NAME = buildConf("datawork.data.center.name")
.doc("数据中心对应中文名称,JSON 数据格式,key为 DATAWORK_DATA_CENTER对应值," +
"例如:{'hangzhou': '杭州数据中心', 'shanghai': '上海数据中心'}")
.version("3.0.0")
.mapStringConf()
.createWithDefault(new MapStringValue("hangzhou", "杭州数据中心"));
}
@Bean("configClient")
@Order(HIGHEST_PRECEDENCE)
public BeeConfigClient buildBeeConfigClient(JdbcTemplate jdbcTemplate) {
String profile = "";
if (ArrayUtils.contains(environment.getActiveProfiles(), "dev")) {
profile = "dev";
} else if (ArrayUtils.contains(environment.getActiveProfiles(), "test")) {
profile = "test";
} else if (ArrayUtils.contains(environment.getActiveProfiles(), "production")) {
profile = "production";
} else {
throw new IllegalArgumentException("profile 值不正确");
}
String sql = "SELECT config_text FROM dc_config where appname='dataworker' and profile='" + profile + "'";
BeeConfigClient configClient = new BeeConfigClient(jdbcTemplate, sql, DataWorkerConf.class, "datawork.");
return configClient;
}
//使用方法
String[] tidbAdmin = configClient.getStringArray(DATAWORK_TIDB_ADMINISTRATORS);
int colLimit = configClient.getInteger(DATAWORK_TABLE_PREVIEW_COLNMN_MAX_NUM);
```
### 七、基于spring security扩展的账号管理
```sql
-- 确保用户表有email 字段
ALTER TABLE user
ADD COLUMN `email` VARCHAR(256) NULL DEFAULT null COMMENT '用户邮箱' AFTER `status`,
ADD COLUMN `sec_enabled` TINYINT(1) NULL DEFAULT 1 COMMENT '表示是否可用,0 无效,1 有效' AFTER `email`,
ADD COLUMN `sec_credentials_expired_date` DATETIME NULL DEFAULT null COMMENT '密码过期时间' AFTER `sec_enabled`,
ADD COLUMN `sec_account_expired_date` DATETIME NULL DEFAULT NULL COMMENT '账号过期时间' AFTER `sec_credentials_expired_date`,
ADD COLUMN `sec_locked_date` DATETIME NULL DEFAULT NULL COMMENT '账号锁定到期时间' AFTER `sec_account_expired_date`,
ADD COLUMN `sec_non_update_pw` TINYINT(1) NULL DEFAULT 1 COMMENT '系统分配默认账号,或者重置密码,首次登录需要强制修改密码, 0 需要更新密码,1 不需要更新密码,' AFTER `sec_locked_date`,
ADD COLUMN `sec_attempts` INT NULL DEFAULT 0 COMMENT '密码尝试登录次数, 超过一定次数,锁定一段时间' AFTER `sec_non_update_pw`,
ADD COLUMN `sec_login_date` DATETIME NULL DEFAULT NULL COMMENT '最近登录时间' AFTER `sec_attempts`,
ADD COLUMN `sec_confirm` TINYINT(1) NULL DEFAULT 0 COMMENT '账号是否验证通过,0 未验证,1 验证' AFTER `sec_login_date`,
ADD COLUMN `sec_comfirm_token` VARCHAR(256) NULL DEFAULT NULL COMMENT '账号验证token' AFTER `sec_confirm`,
ADD COLUMN `sec_comfirm_expired_date` DATETIME NULL DEFAULT NULL COMMENT '账号验证token过期时间' AFTER `sec_comfirm_token`;
```
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 静态资源不走spring security 过滤器链,效率更高,
* 参考:https://xie.infoq.cn/article/7ec9773eb2f9f493cadd18855
*
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**/*.js", "/**/*.css", "/**/*.png", "/datainsight.ico", "/ok.htm");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.cors()
.and()
.authorizeRequests()
.antMatchers("/index.html").permitAll()
.antMatchers("/api/**").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/changePassword").permitAll()
.antMatchers("/user/changePassword").permitAll()
.antMatchers("/version").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.failureHandler(new UserNameAuthenticationFailureHandler("/login", "/changePassword"))
.successHandler(authenticationSuccessHandler())
.and()
.csrf().disable()
.and()
.exceptionHandling()
.authenticationEntryPoint(new CustomLoginUrlAuthenticationEntryPoint("/login"))
.and()
.logout().logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe()
.userDetailsService(new LdapUserDetailsService())
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(15 * 24 * 60 * 60); //15天
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("TDDISESSIONID");
serializer.setCookiePath("/");
serializer.setCookieMaxAge(604800);
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
@Autowired
public void configureGlobal(
AuthenticationManagerBuilder auth,
PasswordEncoder passwordEncoder) throws Exception {
auth.authenticationProvider(authenticationProvider(passwordEncoder));
}
@Bean
public BeeUserDetailsService userDetailsService() {
BeeUserDetailsService userDetailsService = new BeeUserDetailsService();
userDetailsService.setUserTableName("di_user");
userDetailsService.setUserIdColumnName("username");
userDetailsService.setCnNameColumnName("name");
userDetailsService.setJdbcTemplate(jdbcTemplate);
return userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
}
@Bean
public AuthenticationProvider authenticationProvider() {
BeeAuthenticationProvider provider = new BeeAuthenticationProvider();
provider.setMaxLoginFailures(6);
provider.setPasswordEncoder(Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
provider.setUserDetailsService(userDetailsService());
return provider;
}
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
BeeAuthenticationSuccessHandler handler = new BeeAuthenticationSuccessHandler(userDetailsService());
handler.setDefaultTargetUrl("/");
handler.setAlwaysUseDefaultTargetUrl(true);
return handler;
}
}
```
### 八、查询数据库元数据信息+执行sql
主要用于多数据源场景,屏蔽不同元数据差异。支持:MySQL,Oracle,SqlServer, StarRocks,Doris,DB2,Hive,Inceptor,PG、Phoenix、Inceptor、Hana等数据库。
```java
-- 查询hive 表元数据
ConnectionDesc connection = ConnectionDesc.builder()
.withDataSourceType(DataSourceType.HIVE)
.withUrl("jdbc:hive2://10.0.8.2:10000/;principal=hive/sugar-1@EXAMPLE.COM")
.withPrincipal("hive/sugar-1@EXAMPLE.COM")
.withKeytabFile("/Users/melin/Documents/codes/superior/superior/src/test/resources/hive.keytab")
.withKrb5File("/Users/melin/Documents/codes/superior/superior/src/test/resources/kerberos.conf")
.build();
JdbcDialect dialect = JdbcDialectHolder.getJdbcDialect(connection);
DataSourceInfo dataSourceInfo = dialect.testConnection();
System.out.println(dataSourceInfo.getDatabaseProductVersion());
Set schemaNames = dialect.getSchemaNames();
System.out.println(schemaNames);
CompletableFuture future = dialect.asyncExecuteSql("show databases");
QueryResult queryResult = future.get();
System.out.println(JsonUtils.toJSONString(queryResult, true));
-- MySQL
Map properties = Maps.newHashMap();
// 使用 HikariCP 数据源,参数请参考:https://github.com/brettwooldridge/HikariCP
properties.put("maximumPoolSize", "2");
ConnectionDesc connection = ConnectionDesc.builder()
.withDataSourceType(DataSourceType.MYSQL)
.withUrl("jdbc:mysql://172.18.5.41:3006")
.withUsername("root")
.withPassword("root2022")
.withProperties(properties)
.build();
JdbcDialect dialect = JdbcDialectHolder.getJdbcDialect(connection);
DataSourceInfo dataSourceInfo = dialect.testConnection();
System.out.println(dataSourceInfo.getDatabaseProductVersion());
dialect.close()
```
> 1. [聊聊jdbc socketTimeout的设置](https://segmentfault.com/a/1190000012944562)
> 2. [关于 Hive JDBC 的超时问题](https://tianshuang.me/2021/07/%E5%85%B3%E4%BA%8E-Hive-JDBC-%E7%9A%84%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98/index.html)
### 九、解析jdbc url: JdbcURLParser
```java
JdbcURLInfo connectionInfo = JdbcURLParser.parse("jdbc:mysql//primaryhost/test");
assertEquals(MYSQL, connectionInfo.getDsType());
assertEquals("test", connectionInfo.getDbInstance());
assertEquals("primaryhost:3306", connectionInfo.getDbPeer());
JdbcURLInfo connectionInfo = JdbcURLParser
.parse("jdbc:redshift://redshift-test2.abcdefg.us-east-1.redshift.amazonaws.com:5439/sample?ssl=true&tcpKeepAlive=true");
assertEquals(REDSHIFT, connectionInfo.getDsType());
assertEquals("redshift-test2.abcdefg.us-east-1.redshift.amazonaws.com:5439", connectionInfo.getDbPeer());
assertEquals("sample", connectionInfo.getDbInstance());
assertEquals("ssl=true&tcpKeepAlive=true", connectionInfo.getDbOptions());
```
### 十、SPI 扩展
从dubbo中精简抽取出来的代码, 推荐使用pf4j(https://pf4j.org/),功能比较丰富,支持classloader 隔离
```java
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Fruit.class);
Fruit fruit = extensionLoader.getDefaultExtension();
Assert.assertEquals("apple", fruit.printName());
fruit = extensionLoader.getExtension("banana");
Assert.assertEquals("banana", fruit.printName());
```
## 参考文档
1. [Managing multi-module dependencies with Maven assembly plugin](https://stackoverflow.com/questions/9033920/managing-multi-module-dependencies-with-maven-assembly-plugin)