Git Product home page Git Product logo

choerodon-starters's People

Contributors

aimzhangjian avatar azengqiang avatar berubou avatar caojiameng avatar chenshinan avatar crockitwood avatar cyjrun avatar dengyouquan avatar devane001 avatar devil-scp avatar dinghuang avatar flyleft avatar huangfuqiang avatar huaxindeng avatar jalynxing avatar longhe1996 avatar mikane200 avatar superlee007 avatar thefuckingcode avatar timebye avatar wang-ha0 avatar zhuzhiyang avatar zmfcn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

choerodon-starters's Issues

[0.8.1.RELEASE] TiDB数据库环境下choerodon-tool-liquibase 执行groovy脚本连接断开

TiDB 数据库环境下使用choerodon-tool-liquibase执行groovy 脚本时会报出连接断开的错误。

liquibase.exception.LockException: liquibase.exception.UnexpectedLiquibaseException: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.

根据报错信息发现问题是liquibase.lockservice.StandardLockService#init方法在数据库中生成的databasechangeloglock表中LOCKEDbit类型,liquibase.lockservice.StandardLockService#acquireLock方法在获取锁时会将该字段改为1TiDBbit类型更新为1时会发生错误2013 - Lost connection to MySQL server during query(应该是TiDB的BUG)。 在liquibase 官方文档中标注的LOCKEDint类型。但是代码并不是int类型,可以通过覆盖文件liquibase.sqlgenerator.core.CreateDatabaseChangeLogLockTableGenerator#generateSql修改字段类型来解决这个问题。

[master] SqlServerParser排序列自动拼接到SQL查询列中的问题

// io/choerodon/mybatis/pagehelper/parser/SqlServerParser.java:449
selectMap.put(expItem.getExpression().toString(), expItem);

上句代码将查询字段中的别名也作为KEY存到了Map中。

// io/choerodon/mybatis/pagehelper/parser/SqlServerParser.java:469
SelectExpressionItem selectExpressionItem = selectMap.get(expression.toString());

上句代码判断排序列是否存在于查询列中时去获取KEY是否存在,排序字段是可以不写表别名的,而且@io.choerodon.mybatis.pagehelper.annotation.SortDefault注解中指定的排序字段也不会添加表别名。
当多表关联,并且排序字段存在于多表中,又没有写表别名时,会抛出异常

com.microsoft.sqlserver.jdbc.SQLServerException: Ambiguous column name 'xxx'.

choerodon-starter-mybatis-mapper简介

choerodon-starter-mybatis-mapper

mybatis基础工具包,集成通用MapperPageHelper两个开源项目,对源代码根据自身业务逻辑需求进行了精简和修改,扩展了审计字段、多语言功能,并修改了分页插件,添加了插入或更新指定列等功能。

Choerodon微服务里有数据库操作的都使用了这个工具包

Feature

PageHelper嵌套结果查询分页处理,分页查询通用方法抽象。

To get the code

git clone https://rdc.hand-china.com/gitlab/io.choerodon/choerodon-starter-parent.git

Installation and Getting Started

<dependency>
    <groupId>io.choerodon</groupId>
    <artifactId>choerodon-starter-mybatis-mapper</artifactId>
    <version>0.1.0</version>
</dependency>

Documentation

通用Mapper原作者文档

PageHelper原作者使用方法

通用Mapper实现原理的介绍

Usage

通用Mapper用法:

新建user表:

CREATE TABLE `user`  (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'name',
  `object_version_number` bigint(20) UNSIGNED NULL DEFAULT 1,
  `created_by` bigint(20) UNSIGNED NULL DEFAULT 0,
  `creation_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
  `last_updated_by` bigint(20) UNSIGNED NULL DEFAULT 0,
  `last_update_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 867 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO `user` VALUES (1, 'Daenerys Targaryen', 1, 0, '2018-04-11 03:09:38', 0, '2018-04-11 03:09:38');

对应的dataobject如下:

//对应数据库的表名
@Table(name = "user")
//支持4个审计字段(created_by, creation_date, last_update_by, last_update_date)
@ModifyAudit
//object_version_number
@VersionAudit
public class UserDO extends AuditDomain {
    //指定主键,用于插入后主键回查
    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    private String name;

    //省略get和set方法
}

choerodon-starter-mybatis-mapper设置了扫描项目下以mapper结尾的文件夹,因此mapper文件和对应的xml文件应放倒mapper文件夹下面,iam结构如下:

.
+-- src
    +-- main
        +-- docker
        +-- java
        |    +-- io
        |        +-- choerodon
        |            +-- iam
        |               +-- infra
        |                   +-- mapper
        +-- resource
            +-- mapper

mapper文件如下:

public interface UserMapper extends BaseMapper<UserDO> {
    //只是个例子,其实可以用自动生成的方法selectAll()来替代
    List<UserDO> selectAllUsers();
}

继承了BaseMapper的mapper接口包含了绝大多数单表的增删改查操作,可以满足大多数的简单数据库操作,不用手写sql。

如果有复杂的业务逻辑需要手写sql,需要在resource下的mapper文件夹创建与mapper class同名的xml文件,本例中就新建UserMapper.xml,id与方法名相同

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="io.choerodon.iam.infra.mapper.UserMapper">
     <select id="selectAllUsers" resultType="io.choerodon.manager.infra.dataobject.UserDO">
        select * from user
    </select>
</mapper>

目前为止就可以在reposity里面调用userMapper做增删改查操作了。

@Component
public class UserRepositoryImpl implements UserRepository {
    private UserMapper userMapper;
    public UserRepositoryImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    List<UserDO> selectAllUsers() {
        return userMapper.selectAllUsers();
    }
}

同时我们还新建了BaseService类,以组合的方式封装了BaseMapper的方法。需要注意的是insertOptionalupdateOptional两个方法,他们都有一个可变参数,用来传如数据库对应的列,只插入或更新指定列的数据。

如果不使用BaseService里面的insertOptionalupdateOptional方法,而是直接调用userMapper.insertOptional(userDO),如下:

String columns = "id,name,object_version_number";
OptionalHelper.optional(Arrays.asList(columns));
userMapper.insertOptional(userDO);

PageHelper用法:

假如有如下请求

http://localhost:8030/v1/organization/1/users?page=0&size=10&sort=id,desc&sort=organizationId,phone,asc

page是起始页,默认值为0,size是当前页数显示记录数,默认值为20。sort为排序字段,权重从左向右递减,上例表示该查询先按id降序排列,id相同按organizationId升序排列,organizationId相同按phone升序排列。

controller如下:

@ApiOperation(value = "分页查询")
//自定义swagger中pageRequest对象的显示
@CustomPageRequest
@GetMapping
public ResponseEntity<Page<UserDTO>> list(@PathVariable(name = "organization_id") Long organizationId,
                                          @ApiIgnore
                                          //如果请求url里面没有传sort字段,设置默认排序为根据id升序排列
                                          @SortDefault(value = "id", direction = Sort.Direction.ASC)
                                                PageRequest pageRequest,
                                          @RequestParam(required = false) String name)
    return new ResponseEntity<>(organizationUserService.pagingQuery(pageRequest, name), HttpStatus.OK);
}

PageRequest对象对前端传入的page,size和sort参数进行封装。分页查询使用PageHelper对象调用分页方法。

    //只做分页
    int page = pageRequest.getPage();
    int size = pageRequest.getSize();
    Page<UserDO> users = PageHelper.doPage(page, size, () -> userMapper.selectAllUsers());
    //分页和排序
    Page<UserDO> users = PageHelper.doPageAndSort(pageRequest, () -> userMapper.selectAllUsers());
    //只排序
    Sort sort = PageHelper.getSort();
    List<UserDO> users = PageHelper.doSort(sort, () -> userMapper.selectAllUsers());

关于排序的用法下面做详细介绍:

1.单表情况:

单表查询操作不牵扯别名问题,用法简单。controller写法如下:

public ResponseEntity<Page<IconDTO>> pagingQuery(@SortDefault(value = "lastUpdateDate", direction = Sort.Direction.ASC) PageRequest pageRequest)

在controller参数中使用pageRequest对象接收url中的page、size和sort字段。@SortDefault注解value为默认排序字段,如果是驼峰,拼接sql会转为下划线;direction为升序(Sort.Direction.ASC)和降序(Sort.Direction.DESC)。page和size不传分配默认值,sort不传如果controller配置有@SortDefault注解,则以注解生成默认值,如果没有配置注解则sort为null。

方法调用如下:

//如果使用生成sort的方式,xml中的sql请不要写order by。
PageHelper.doPageAndSort(pageRequest, ()-> mapper.fulltextSearch(userDO, param));

2.关联多表查询(只适合简单的关联表查询,如果是极复杂查询请自己写sql):

    SELECT
        fo.id,
        fo.code,
        fo.name,
        fo.password_policy_id,
        fp.id AS project_id,
        fp.name AS project_name,
        fp.code AS project_code,
        fp.organization_id,
        fp.is_enabled
    FROM
        fd_organization as fo
    LEFT
        JOIN fd_project as fp
    ON
        fo.id = fp.organization_id
    WHERE
        fp.is_enabled = true

由于organization和project表都有code和name字段,如果要对organization表的code字段和project表的code字段进行排序,前端url对sort字段传参要做区分,假如是sort=organizationCode,projectCode,desc,由于sql中存在别名现象且前端传入的排序字段名也存在认为命名的因素,所以需要建立一个HashMap进行key-value映射,organizationCode指向fo.code,projectCode指向fp.code。
这里将fo定义为主表,fp定义为从表。下例是按照organization表的code、name字段和project表的code、name字段排序:

   Map<String, String> map = new HashMap<>();
   map.put("organizationCode", "fo.code");
   map.put("projectCode", "fp.code");
   map.put("organizationName", "fo.name");
   map.put("projectName", "fp.name");
   //设置主表的别名,@SortDefault的value=id时,值会拼接到fo.id
   pageRequest.resetOrder("fo", map);
   PageHelper.doPageAndSort(pageRequest, ()-> mapper.fulltextSearch());

pageRequest.resetOrder("fo", map)必须写,重置sort里面的order对象,第一个参数为主表别名,没有别名写为fd_organization,第二个参数为map映射关系。主表别名的作用是如果sort=id,asc,会在上面的sql末尾拼接 order by fo.id asc,如果要还想按project_id排序,只能放map里面,用
map.put("projectId", "fp.id")的形式实现。
设置完pageRequest后调用PageHelper.doPageAndSort()方法即可。

PageRequest在feign调用时传递

feign调用传递分页参数的�时候,传递PageRequest对象,feign会把这个对象当成body,调用post方法请求。但PageRequest解析器是从url里的?后面截取参数然后放到�PageRequest对象里,默认的feign编码器会导致调用的时候分页参数丢失,所以mapper里面提供了一个�PageRequestQueryEncoder,在客户端服务做feign配置如下:

@Configuration
public class FeignClientConfig {

    private ObjectFactory<HttpMessageConverters> messageConverters;

    FeignClientConfig(ObjectFactory<HttpMessageConverters> messageConverters) {
        this.messageConverters = messageConverters;
    }

    @Bean
    public Encoder feignEncoder() {
        return new PageRequestQueryEncoder(new SpringEncoder(messageConverters));
    }
}

feign调用的时候直接传PageRequest对象即可:

    @GetMapping("/v1/organizations")
    ResponseEntity list(PageRequest pageRequest);

xml中存在不兼容的数据库方言怎么处理

mapper提供了一些�常用的DatabaseIdProvider

    DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
    Properties properties = new Properties();
    properties.setProperty("Oracle", "oracle");
    properties.setProperty("MySQL", "mysql");
    properties.setProperty("DB2", "db2");
    properties.setProperty("Derby", "derby");
    properties.setProperty("H2", "h2");
    properties.setProperty("HSQL", "hsql");
    properties.setProperty("Informix", "informix");
    properties.setProperty("MS-SQL", "ms-sql");
    properties.setProperty("PostgreSQL", "postgresql");
    properties.setProperty("Sybase", "sybase");
    properties.setProperty("Hana", "hana");
    databaseIdProvider.setProperties(properties);

对于一些不兼容的sql,比如分页方言(这里只是做举例),mysql/oracle/sqlserver都不相同,这个时候只需要在xml里面提供databaseId即可

<select id="list" databaseId="mysql" resultMap="user">
    select * from user limit 10
</select>
<select id="list" databaseId="oracle" resultMap="user">
    <![CDATA[
    select * from user where ROWNUM <= 10
     ]]>
</select>

注意

单表动态排序有字段校验,如果传入字段不是数据库字段,抛异常。

多表动态排序暂时没有非法字段校验,如果字段非法,只有在执行sql的时候抛SqlGrammarException,但可以防止sql注入。

这种在sql后面拼order by的操作只能在sql语句的末尾拼接,如果有分页参数,就是在sql语句末尾,分页参数之前拼接。不支持嵌套结果查询在sql的中间部分拼接order by。如果要自定义order by位置目前只能自己手动写sql,把排序字段以参数的形式传入。

Dependencies

  • choerodon-starter-core: Page对象和PageInfo对象
  • mybatis-spring-boot-starter
  • com.github.jsqlparse
  • com.fasterxml.jackson.core
  • javax.persistence
  • mysql-connector-java
  • swagger-annotations

Reporting Issues

If you find any shortcomings or bugs, please describe them in the Issue.

How to Contribute

Pull requests are welcome! Follow this link for more information on how to contribute.

Note

  • 不是表中字段的属性必须加 @Transient注解,这样生成动态sql就不会获取到该列

  • 在实体类要在类前面加@MultiLanguage注解,开启多语言支持,多语言字段需要加@MultiLanguageField注解

  • 通用 Mapper 不支持 devtools 热加载,devtools 排除实体类包即可,配置方式参考:sing-boot-devtools-customizing-classload

  • 只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页。

  • 请不要配置多个分页插件

  • 请不要在系统中配置多个分页插件(使用Spring时,mybatis-serviceConfig.xml和Spring配置方式,请选择其中一种,不要同时配置多个分页插件)!

  • 分页插件不支持带有for update语句的分页

  • 对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

  • 分页插件不支持嵌套结果映射,由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。

jsqlparser升级后SqlServerParser报错

choerodon-starter-mybatis-mapper-0.6.4.RELEASE.jar中依赖的jsqlparser0.9.5,最新的jsqlparser版本为

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>1.3</version>
</dependency>

因为在最新的版中添加了更多的SQL语法支持。
io/choerodon/mybatis/pagehelper/parser/SqlServerParser.java:78中调用了net.sf.jsqlparser.statement.select.Top#setRowCount方法。

// TODO instead of a plain number, an expression should be added, which could be a NumberExpression, a GroupedExpression or a JdbcParameter
public void setRowCount(long rowCount) {
    this.rowCount = rowCount;
}

1.3版本中该方法已经被移除,当使用SQL Server数据库时启动会抛出NoSuchMethodException

starter-config-client 路由定位器解析路由BUG

image
image
image
BeanUtils.copyProperties 方法在反射赋值时,调用ZuulRoute.setSensitiveHeaders方法时,始终都会将customSensitiveHeaders设置为true,导致全局设置的过滤敏感头信息不生效。
建议先判断sensitiveHeaders列表非空再调这个方法。

这个依赖在没上传??

        <!--<groupId>io.choerodon</groupId>-->
        <!--<artifactId>choerodon-base-api</artifactId>-->
        <!--<version>0.10.1.RELEASE</version>-->
    <!--</dependency>-->

Why can not user PageHelper.doPageAndSort to sort field which not in DO/Entity

see these codes in io.choerodon.mybatis.pagehelper.OrderByParser#getColumn(Class<?>, String)

//前端传入字段与do对象字段对比,不匹配抛非法参数异常
for (EntityColumn entityColumn : columnList) {
    if (entityColumn.getProperty().toLowerCase().equals(camelHumpProperty.toLowerCase())) {
        return entityColumn.getColumn();
    }
}
throw new IllegalArgumentException("Illegal sort argument: " + property);

now my project neet to sort the result of a sql using INNER JOIN, and the sort field is NOT in the basic table. therefore, I create a @Transient field in entity/DO like this:

@Id
@GeneratedValue
private Long labelId;

@NotEmpty
@Size(max = 64)
private String labelName;

@NotEmpty
@Size(max = 32)
private String labelTypeCode;

@Transient
private String creator;

then I user this transient field to sort:

@GetMapping
public ResponseEntity<Page<Label>> list(
                Label label,
                @SortDefaults(
                    {@SortDefault(sort = Label.FIELD_LABEL_ID, direction = Direction.DESC),
                    @SortDefault(sort = Label.FIELD_CREATOR, direction = Direction.ASC)}
                ) PageRequest pageRequest
            ){
    Page<Label> labels = this.labelService.pageAndRequest(pageRequest, label);
    return ResponseEntity.ok(labels);
}

and when I request this api and get

IllegalArgumentException("Illegal sort argument:  creator");

so WHY do we want to limit that the sort field must include in entity/DO? This is too strict to use.

THX

没有正确处理心跳,websocket保活并不启作用.

choerodon-starters 中的 choerodon-websocket-helper 对于 websocket 的长连接心跳并不能检测出链接的失效.

由于我们有很多集群需要管理,有些是通过公网连接.时不时会有集群认为是健康的,但是无法发送命令的集群发生.我查看了代码,直到最新的 devops tag 中仍然用的是 choerodon-websocket-helper 项目,其从0.7.1(我们用的)至当前的 0.11.1在这块代码几乎是一样的.

 @Scheduled(initialDelay = 10*1000,fixedRate = 10*1000)
  public void sendPing(){
      List<Session> sessions = sessionRepository.allSessions();
      for (Session session : sessions){
         try {
           session.getWebSocketSession().sendMessage(new PingMessage());
         } catch (Exception e) {
            sessionRepository.removeById(session.getUuid());
            logger.error("remove disconnected ");
          }
    }
}

这里的心跳检测依赖发送是否成功,但是这里是有问题的.
用户空间的写入只是表示成功将数据从用户空间 copy 至内核空间而已,之后由 tcp 的协议处理器去发送.
所以这里我认为正常的做法,发送了 PingMessage得必须在限定时间内得到 PongMessage 响应才表示链接是健康的.

agent 直到最新的版本都是直接丢弃 PingMessage 没有做任何处理.

我认为正确的做法是应该 agent-devops 都需要发送 PingMessage 并等待对方的 PongMessage,达到阀值后(超时+次数)即认为链接不健康断开等待 agent 发起重连.

第二个问题,由于心跳就算正常处理了,但是仍然会有心跳确认中这个时间窗口链接已经失效了,但是在发送消息的时候虽然用的是 spring 的 websocket 实现,却没有实现

public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception}

这个方法,这里建议应该最次要将链接断开等待发起重连.

如果可以,我可能会发起一个 PR,不过 master 中已经放弃了 choerodon-websocket-helper 改为了 choerodon-starts-websocket 并重新实现了,不过问题好像仍然存在.
由于我们仍然使用了 0.11.1 的 choerodon,所以我只能 fork 自己修改一下旧有的版本.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.