baomidou / mybatis-plus

An powerful enhanced toolkit of MyBatis for simplify development
https://baomidou.com
Apache License 2.0
16.42k stars 4.31k forks source link

postgres uuid 自定义typehandler 和 自定义主键生成策略,主键赋值时出错。 #6337

Closed allendata0706 closed 3 months ago

allendata0706 commented 4 months ago

请详细描述需要增加的功能

依赖版本:

 <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>3.5.7</version>
   </dependency>

uuid handler

@MappedTypes({UUID.class})
public class UuidTypeHandler implements TypeHandler<UUID> {
    private static final Logger LOG = LoggerFactory.getLogger(UuidTypeHandler.class);

    @Override
    public void setParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setObject(i, null, Types.OTHER);
        } else {
            ps.setObject(i, parameter.toString(), Types.OTHER);
        }

    }

    @Override
    public UUID getResult(ResultSet rs, String columnName) throws SQLException {
        return toUUID(rs.getString(columnName));
    }

    @Override
    public UUID getResult(ResultSet rs, int columnIndex) throws SQLException {
        return toUUID(rs.getString(columnIndex));
    }

    @Override
    public UUID getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return toUUID(cs.getString(columnIndex));
    }

    private static UUID toUUID(String val) {
        if (StringUtils.isNotBlank(val)) {
            try {
                return UUID.fromString(val);
            } catch (IllegalArgumentException e) {
                LOG.warn("Bad UUID found: {}", val);
            }
        }
        return null;
    }

}

自定义生成主键ID

@Component
@Slf4j
public class CustomIdGenerator implements IdentifierGenerator {

    private final Sequence sequence = new Sequence(InetAddress.getLoopbackAddress());

    @Override
    public Long nextId(Object entity) {

        //可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成.
        String bizKey = entity.getClass().getName();
        log.info("bizKey:{}", bizKey);
        MetaObject metaObject = SystemMetaObject.forObject(entity);
        String name = (String) metaObject.getValue("name");
        final long id = sequence.nextId();
        log.info("为{}生成主键值->:{}", name, id);
        // 返回生成的ID值
        return id;
    }

    @Override
    public String nextUUID(Object entity) {
        return UUID.randomUUID().toString();
    }
}

### 实体类

@Getter
@Setter
public class Actor implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ASSIGN_UUID)
    private UUID id;

    private String name;

    private JSON configuration;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

mbatis-plus 生成主键,赋值

image

由于实体类的主键类型是UUID, 这里赋值一个字符串会报错 Caused by: org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.echoai.entity.Actor' with value '035260f6-5026-4bd2-b364-ee3311fd4fbc' Cause: java.lang.IllegalArgumentException: argument type mismatch at org.apache.ibatis.reflection.wrapper.BeanWrapper.setBeanProperty(BeanWrapper.java:181) at org.apache.ibatis.reflection.wrapper.BeanWrapper.set(BeanWrapper.java:61) at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:119)

请问,这里的nextuuid() 是不是支持范型比较好呢?

miemieYaho commented 4 months ago

请使用string,不打算支持泛型

allendata0706 commented 4 months ago

调通成功,完整示例如下

依赖版本:

 <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>3.5.7</version>
   </dependency>

jdbc连接参数

数据库配置加上 stringtype=unspecified 官方对stringtype参数的解释是: stringtype : String Specify the type to use when binding PreparedStatement parameters set via setString(). If stringtype is set to VARCHAR (the default), such parameters will be sent to the server as varchar parameters. If stringtype is set to unspecified, parameters will be sent to the server as untyped values, and the server will attempt to infer an appropriate type. This is useful if you have an existing application that uses setString() to set parameters that are actually some other type, such as integers, and you are unable to change the application to use an appropriate method such as setInt().

spring:
  datasource:
    url: jdbc:postgresql://${spring.datasource.host}:${spring.datasource.port}/${spring.datasource.database}?stringtype=unspecified

uuid handler

@MappedTypes({UUID.class})
public class UuidTypeHandler implements TypeHandler<UUID> {
    private static final Logger LOG = LoggerFactory.getLogger(UuidTypeHandler.class);

    @Override
    public void setParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setObject(i, null, Types.OTHER);
        } else {
            ps.setObject(i, parameter.toString(), Types.OTHER);
        }

    }

    @Override
    public UUID getResult(ResultSet rs, String columnName) throws SQLException {
        return toUUID(rs.getString(columnName));
    }

    @Override
    public UUID getResult(ResultSet rs, int columnIndex) throws SQLException {
        return toUUID(rs.getString(columnIndex));
    }

    @Override
    public UUID getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return toUUID(cs.getString(columnIndex));
    }

    private static UUID toUUID(String val) {
        if (StringUtils.isNotBlank(val)) {
            try {
                return UUID.fromString(val);
            } catch (IllegalArgumentException e) {
                LOG.warn("Bad UUID found: {}", val);
            }
        }
        return null;
    }

}

实体类

@Getter
@Setter
public class Actor implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ASSIGN_UUID)
    private String id;

    private String name;

    private JSON configuration;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

生成代码

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.junit.jupiter.api.Test;

import java.nio.file.Paths;
import java.sql.Types;
import java.util.Map;

import static com.baomidou.mybatisplus.generator.config.OutputFile.xml;

public class CodeGeneratorTest {

    @Test
    public void generator() {
        FastAutoGenerator.create("jdbc:postgresql://127.0.0.1:5432/echo-ai", "postgres", "echo@1234567")
                .globalConfig(builder -> builder
                                .author("admin")
                                .disableOpenDir()
                                .outputDir(Paths.get(System.getProperty("user.dir")) + "/src/main/java")
//                        .enableSwagger() // 开启 swagger 模式
                                .commentDate("yyyy-MM-dd")
                )
                .dataSourceConfig(builder ->
                        builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                            System.out.println("metaInfo.getTypeName : " + metaInfo.getTypeName());
                            // uuid
                            if (metaInfo.getTypeName().equals("uuid")) {
                                return new UUIDColumnType();
                            }
                            // jsonb
                            if (metaInfo.getTypeName().equals("jsonb")) {
                                return new JSONColumnType();
                            }
                            int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                            if (typeCode == Types.SMALLINT) {
                                // 自定义类型转换
                                return DbColumnType.INTEGER;
                            }
                            return typeRegistry.getColumnType(metaInfo);
                        })
                )
                .packageConfig(builder -> builder
                        .parent("com.echo.ai.repository")
                        .entity("entity")
                        .mapper("mapper")
                        .service("service")
                        .serviceImpl("service.impl")
                        .xml("mapper.xml")
                        .pathInfo(Map.of(xml, Paths.get(System.getProperty("user.dir")) + "/src/main/resources/mapper"
                        ))
                )
                .strategyConfig(builder -> {
                            builder.entityBuilder().enableFileOverride().enableLombok().idType(IdType.ASSIGN_UUID);
                            builder.mapperBuilder().enableFileOverride();
                            builder.controllerBuilder().disable();
                            builder.serviceBuilder().disableService();
                            builder.serviceBuilder().disable();
                            builder.addInclude("actor");

                        }
                )
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();

    }

    static class UUIDColumnType implements IColumnType {

        @Override
        public String getType() {
            return "String";
        }

        @Override
        public String getPkg() {
            return java.lang.String.class.getName();
        }
    }

    static class JSONColumnType implements IColumnType {

        @Override
        public String getType() {
            return "JSON";
        }

        @Override
        public String getPkg() {
            return JSON.class.getName();
        }
    }

    // timestampz 类型支持

}
nieqiurong commented 3 months ago

目前版本内置策略不支持,你可以改完input手动赋值.

allendata0706 commented 3 months ago

目前版本内置策略不支持,你可以改完input手动赋值.

java 属性是UUID,主动设置值是OK的

nieqiurong commented 3 months ago

用input,后面版本不会报错,但会打印警告日志.

nieqiurong commented 3 months ago

已发布 3.5.8-SNAPSHOT