Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for deserializing object property values based on value class #510

Merged
merged 9 commits into from
Aug 2, 2024

Conversation

quicklyfast
Copy link
Contributor

Add the getValue method in Type to deserialize JSON data from a ResultSet into an object of the specified class (clazz).

package com.example;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.querydsl.core.types.Path;
import com.querydsl.sql.Configuration;
import com.querydsl.sql.MySQLTemplates;
import com.querydsl.sql.RelationalPathBase;
import com.querydsl.sql.SQLTemplates;
import com.querydsl.sql.mysql.MySQLQueryFactory;
import com.querydsl.sql.types.Type;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * ExampleTest class with QueryDSL configuration and JSON handling for DataSource properties.
 */
@SpringBootConfiguration
@Import(ExampleTest.QueryDSLConfiguration.class)
@SpringBootTest
@EnableAutoConfiguration
class ExampleTest {

    /**
     * Interface representing a JSON entity.
     */
    interface JsonEntity {
    }

    /**
     * JSON type handler using ObjectMapper to convert JSON strings to specified type objects.
     */
    static class JSONType implements Type<JsonEntity> {
        private final ObjectMapper objectMapper;

        public JSONType() {
            objectMapper = new ObjectMapper();
            objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
            objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
            objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        }

        @Override
        public int[] getSQLTypes() {
            return new int[]{Types.BLOB, Types.VARBINARY, Types.VARCHAR, Types.BINARY};
        }

        @Override
        public Class<JsonEntity> getReturnedClass() {
            return JsonEntity.class;
        }

        @Override
        public String getLiteral(JsonEntity value) {
            if (value == null) {
                return null;
            }
            try {
                return objectMapper.writeValueAsString(value);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public JsonEntity getValue(ResultSet rs, int startIndex) throws SQLException {
            try {
                return objectMapper.readValue(rs.getBytes(startIndex), JsonEntity.class);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public JsonEntity getValue(ResultSet rs, int startIndex, Class<JsonEntity> clazz) throws SQLException {
            try {
                return objectMapper.readValue(rs.getBytes(startIndex), clazz);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void setValue(PreparedStatement st, int startIndex, JsonEntity value) throws SQLException {
            st.setObject(startIndex, getLiteral(value));
        }
    }

    /**
     * DataSourceProperties class representing the properties of a data source.
     */
    public static class DataSourceProperties implements JsonEntity {
        private String hostname;
        private int port;
        private String schema;
        private String username;
        private String password;
        private Map<String, Object> connectionProperties;

        public String getHostname() {
            return hostname;
        }

        public void setHostname(String hostname) {
            this.hostname = hostname;
        }

        public int getPort() {
            return port;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public String getSchema() {
            return schema;
        }

        public void setSchema(String schema) {
            this.schema = schema;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public Map<String, Object> getConnectionProperties() {
            return connectionProperties;
        }

        public void setConnectionProperties(Map<String, Object> connectionProperties) {
            this.connectionProperties = connectionProperties;
        }
    }

    /**
     * DataSourceEntity class representing a data source entity.
     *
     * <pre>
     *     CREATE TABLE `data_source`  (
     *      `id` int NOT NULL AUTO_INCREMENT,
     *      `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
     *      `properties` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
     *       PRIMARY KEY (`id`) USING BTREE
     *     ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
     *
     *     INSERT INTO `data_source` VALUES (1, 'mysql-localhost', '{\"hostname\": \"localhost\", \"port\": 3306, \"schema\": \"test\", \"username\": \"root\", \"password\": \"root\", \"connectionProperties\": {\"useSSL\": true, \"useCursorFetch\": true}}');
     * </pre>
     */
    public static class DataSourceEntity {
        private Integer id;
        private String name;
        private DataSourceProperties properties;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public DataSourceProperties getProperties() {
            return properties;
        }

        public void setProperties(DataSourceProperties properties) {
            this.properties = properties;
        }
    }

    /**
     * QDataSourceEntity class representing the QueryDSL path for the DataSourceEntity.
     */
    public static class QDataSourceEntity extends RelationalPathBase<DataSourceEntity> {
        public QDataSourceEntity() {
            this(DataSourceEntity.class, "ds", null, "data_source");
        }

        public QDataSourceEntity(Class<? extends DataSourceEntity> type, String variable, String schema, String table) {
            super(type, variable, schema, table);
        }

        @Override
        public List<Path<?>> getColumns() {
            return Arrays.asList(
                    createNumber("id", Integer.class),
                    createString("name"),
                    createSimple("properties", DataSourceProperties.class)
            );
        }
    }

    /**
     * QueryDSL configuration class.
     */
    static class QueryDSLConfiguration {
        /**
         * SQLTemplates implementation for MySQL.
         */
        @Bean
        public SQLTemplates sqlTemplates() {
            return new MySQLTemplates();
        }

        /**
         * MySQLQueryFactory - main entry point to build and execute SQL queries.
         */
        @Bean
        public MySQLQueryFactory queryFactory(DataSource dataSource) {
            Configuration configuration = new Configuration(new MySQLTemplates());
            configuration.register(new JSONType());
            return new MySQLQueryFactory(configuration, () -> DataSourceUtils.getConnection(dataSource));
        }
    }

    @Autowired
    private MySQLQueryFactory queryFactory;

    @Test
    public void test() {
        QDataSourceEntity qDataSource = new QDataSourceEntity();
        List<DataSourceEntity> list = queryFactory.selectFrom(qDataSource).fetch();
        DataSourceEntity dataSourceEntity = list.get(0);

        assertEquals("localhost", dataSourceEntity.getProperties().getHostname());
    }
}

@quicklyfast quicklyfast requested a review from xeounxzxu August 1, 2024 03:43
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions could not be made:

  • pom.xml
    • lines 494-497
  • querydsl-libraries/querydsl-jpa/pom.xml
    • lines 19-151

@velo
Copy link
Member

velo commented Aug 1, 2024

Hi @quicklyfast, I tried to incorporate some more of your example into querydsl code base

Please let me know what you think

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions could not be made:

  • pom.xml
    • lines 494-497
  • querydsl-examples/pom.xml
    • lines 15-18
    • lines 34-33
  • querydsl-libraries/querydsl-jpa/pom.xml
    • lines 19-151

@velo velo merged commit 04f0a48 into OpenFeign:master Aug 2, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants