java - 将 java.lang.String 类型映射到 Postgres JSON 类型

标签 java json spring postgresql mybatis

我的问题与以下问题有关 Mapping postgreSQL JSON column to Hibernate value type虽然当我在 postgres 中测试将字符串传递给 psql 时答案有效,但从我的 Java 代码传递字符串时它不起作用。

我正在使用 MyBatis 通过 Spring 注入(inject)将 sql 映射到 postgres 数据库。

这是我的 MyBatis Java 接口(interface),我在映射到 Postgres JSON 列时遇到问题的方法是 updateState() 方法。

package receiver.spoke;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import receiver.bean.Spoke;

public interface SpokeDAOMyBatis extends SpokeDAO {

    void updateState(@Param("id") long spokeId, @Param("state") String state);

    String getState(@Param("id") long spokeId);

    List<Spoke> getSpokes();

    // The close() method must exist.
    void close();

}

以下是我的映射器类:

<?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="receiver.spoke.SpokeDAOMyBatis">

    <update id="updateState">
        UPDATE 
            spokes 
        SET 
            state = #{state} 
        WHERE 
            id = #{id}
    </update>

    <select id="getState" resultType="java.lang.String" 
            parameterType="long" useCache="false">
        SELECT 
            state 
        FROM 
            spokes 
        WHERE 
            id = #{id}
    </select>

    <select id="getSpokes" resultMap="Spoke" useCache="false">
        SELECT 
            id, 
            description, 
            uri, 
            updatets 
        FROM 
            spokes
    </select>

    <resultMap type="receiver.bean.Spoke" id="Spoke">
            <id property="id" column="id"/>
            <id property="description" column="description"/>
            <id property="uri" column="uri"/>
            <id property="updatets" column="updatets"/>
    </resultMap>

</mapper>

以下是 Spoke bean 类:

package receiver.bean;

import java.sql.Timestamp;

public class Spoke {

    private long id;
    private String description;
    private String uri;
    private Timestamp updatets;
//    private String state;

    public long getId() {
        return this.id;
    }

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

    public String getDescription() {
        return this.description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getUri() {
        return this.uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public Timestamp getUpdatets() {
        return updatets;
    }

    public void setUpdatets(Timestamp updatets) {
        this.updatets = updatets;
    }

//  public String getState() {
//      return (String) state;
//  }

//  public void setState(String state) {
//      this.state = state;
//  }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.description == null) ? 0 : this.description.hashCode());
        result = prime * result + (int) (this.id ^ (this.id >>> 32));
        result = prime * result + ((this.uri == null) ? 0 : this.uri.hashCode());
        result = prime * result + ((this.updatets == null) ? 0 : this.updatets.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Spoke other = (Spoke) obj;
        if (this.description == null) {
            if (other.description != null)
                return false;
        } else if (!this.description.equals(other.description))
            return false;
        if (this.id != other.id)
            return false;
        if (this.uri == null) {
            if (other.uri != null)
                return false;
        } else if (!this.uri.equals(other.uri))
            return false;
        if (this.updatets == null) {
            if (other.updatets != null)
                return false;
        } else if (!this.updatets.equals(other.updatets))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "SpokeData [id=" + this.id + ", description=" + this.description + ", uri=" + this.uri + "]";
    }

}

以下是我的 applicationContext.xml 文件的代码

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans.xsd"
     default-lazy-init="false">

    <bean id="configurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list merge="true">
                <value>classpath:db.properties</value>
            </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="${test.db.url}" />
        <property name="username" value="${test.db.username}" />
        <property name="password" value="${test.db.password}" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="spokeDAOMyBatis" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="receiver.spoke.SpokeDAOMyBatis" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>

</beans>

以下是db.properties文件

test.db.url=jdbc:postgresql://localhost:5432/lunar
test.db.username=lunar
test.db.password=lunar

下面是mybatis-config.xml文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <setting name="cacheEnabled" value="false" />
        <setting name="lazyLoadingEnabled" value="false" />
        <setting name="defaultExecutorType" value="REUSE" />
        <!-- <setting name="defaultStatementTimeout" value="20000" /> -->
        <setting name="logImpl" value="LOG4J" />
    </settings>
</configuration>

这是包含 json 字段的 spoke 表

CREATE TABLE spokes (
    id BIGSERIAL NOT NULL,
    insertts TIMESTAMPTZ NOT NULL,
    updatets TIMESTAMPTZ NOT NULL,
    rowversion BIGINT NOT NULL,
    description TEXT NOT NULL,
    uri TEXT NOT NULL,
    state JSON
);

-- create primary key
ALTER TABLE spokes ADD CONSTRAINT pk_spokes PRIMARY KEY (id);

-- triggers
CREATE TRIGGER b_spokes_audit BEFORE INSERT OR UPDATE ON spokes FOR EACH ROW EXECUTE PROCEDURE trigger_function_aud$

-- grants
GRANT ALL ON TABLE spokes TO lunar;

接下来创建字符串到json转换的函数

CREATE OR REPLACE FUNCTION json_intext(text) RETURNS json AS $$
SELECT json_in($1::cstring); 
$$ LANGUAGE SQL IMMUTABLE;
CREATE FUNCTION

创建类型转换

CREATE CAST (text AS json) WITH FUNCTION json_intext(text) AS IMPLICIT;
CREATE CAST

这在从我的 psql 内部而不是从 java 端进行转换时有效

以下是一些测试,显示通过 psql 为名为 xxx 的表工作的函数和转换,以及不接受无效 json 的事实

PREPARE test(text) AS INSERT INTO xxx(id,state) VALUES (1,$1);
PREPARE
execute test('{}');
INSERT 0 1
select json_in('a');
ERROR:  invalid input syntax for type json
DETAIL:  Token "a" is invalid.
CONTEXT:  JSON data, line 1: a
select json_in('a'::cstring);
ERROR:  invalid input syntax for type json
DETAIL:  Token "a" is invalid.
CONTEXT:  JSON data, line 1: a
select json_in('{}');
 json_in 
---------
 {}
(1 row)

select json_in('{');
ERROR:  invalid input syntax for type json
DETAIL:  The input string ended unexpectedly.
CONTEXT:  JSON data, line 1: {
select json_in('{}');
 json_in 
 ---------
  {}
(1 row)

以下是我的测试类:

package receiver.spoke;

import static org.junit.Assert.*;

import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbcp.BasicDataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import receiver.bean.Spoke;
import receiver.spoke.SpokeDAOMyBatis;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml" })
public class SpokeDAOMyBatisTest {

    @Autowired 
    @Qualifier("spokeDAOMyBatis")
    private SpokeDAOMyBatis spokeDAOMyBatis;

    @Autowired
    @Qualifier("dataSource")
    private BasicDataSource datasource;

    @Before
    public void connectionTest() throws SQLException {     
        datasource.getConnection();
    }

    @Test
    public void updateState() {

        assertNotNull(spokeDAOMyBatis);
        spokeDAOMyBatis.updateState(1L, "{}");
        assertTrue("Returned wrong state", spokeDAOMyBatis.getState(1L).equals("{}"));
    }

    @Test
    public void getState() {

        assertNotNull(spokeDAOMyBatis);
        String str1 = spokeDAOMyBatis.getState(1L);
        String str2 = spokeDAOMyBatis.getState(2L);
        assertTrue("Returned wrong state", spokeDAOMyBatis.getState(1L).equals(str1));
        assertTrue("Returned wrong state", spokeDAOMyBatis.getState(2L).equals(str2));
    }

    @Test
    public void getAllSpokesTest() {

        assertNotNull(spokeDAOMyBatis);
        List<Spoke> list = spokeDAOMyBatis.getSpokes();
        System.out.println(list.toString());

        assertNotNull("List of spokes returned null", list);
        assertTrue("List of spokes is empty", !list.isEmpty());
        assertNotNull("Invalid spoke", list.get(0));
        assertNotNull("id not loaded", list.get(0).getId());
        assertNotNull("description not loaded", list.get(0).getDescription());
        assertNotNull("uri not loaded", list.get(0).getUri());
        assertNotNull("updatets not loaded", list.get(0).getUpdatets());
    }    
}

所以这一切似乎都工作正常,但是当我尝试运行 Java 端时,我得到了转换异常,如下面的堆栈跟踪所示:

Tests run: 3, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.241 sec <<< FAILURE!
updateState(receiver.spoke.SpokeDAOMyBatisTest)  Time elapsed: 0.035 sec  <<< ERROR!
org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: org.postgresql.util.PSQLException: ERROR: column "state" is of type json but expression is of type character varying
  Hint: You will need to rewrite or cast the expression.
  Position: 38
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: UPDATE     spokes    SET     state = ?    WHERE     id = ?
### Cause: org.postgresql.util.PSQLException: ERROR: column "state" is of type json but expression is of type character varying
  Hint: You will need to rewrite or cast the expression.
  Position: 38
; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: ERROR: column "state" is of type json but expression is of type character varying
  Hint: You will need to rewrite or cast the expression.
  Position: 38
    at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:231)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:74)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:399)
    at com.sun.proxy.$Proxy17.update(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:269)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:55)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
    at com.sun.proxy.$Proxy24.updateState(Unknown Source)
    at receiver.spoke.SpokeDAOMyBatisTest.updateState(SpokeDAOMyBatisTest.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: org.postgresql.util.PSQLException: ERROR: column "state" is of type json but expression is of type character varying
  Hint: You will need to rewrite or cast the expression.
  Position: 38
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2270)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1998)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:570)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:420)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:413)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:169)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:59)
    at com.sun.proxy.$Proxy23.execute(Unknown Source)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:45)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:73)
    at org.apache.ibatis.executor.ReuseExecutor.doUpdate(ReuseExecutor.java:51)
    at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:115)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:170)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:386)
    ... 42 more

3601 [Thread-0] INFO  org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@4fcd19b3: startup date [Mon Jan 11 11:10:54 GMT 2016]; root of context hierarchy

最佳答案

将 stringtype=unspecified 添加到我的 jdbc 连接字符串中作为 https://stackoverflow.com/users/1499698/pozs建议工作:

jdbc:postgresql://localhost:5432/dbname?stringtype=unspecified

这样做我还可以删除该函数并将其从字符串转换为 json。

我还通过将无效的 json 传递给 JSON 列来测试更改。

正如我希望的那样,我得到了一个 DataIntegrityViolationException,正如我对该测试所期望的那样

关于java - 将 java.lang.String 类型映射到 Postgres JSON 类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34720231/

相关文章:

java - Mockito @InjectMocks 未将 @Spy 分配给正确的字段

java - 解码 Web 服务响应时出错

java - 切换 JTextField 可见性

java - 为什么 ByteBuffer.getFloat 返回 0.0?

javascript - AJAX 回调中变量值错误

java - 如何使用 flexjson.deserializer 在 java 中将 jsonarray 反序列化为 List<Map>?

json - 如何在 QT 中将 JSON 字符串格式化为可读输出

spring - 为什么 Rest Controller 给出错误 : "No adapter for handler"?

java - 如何在Spring中为html创建 View 解析器?

java - 如何在模块化 Android 应用程序中共享依赖项