PostgreSQL数据类型使用

postgresql使用学习

如果你看到这篇文章,相信你对postgresql已经有所了解,但是对postgresql的使用可能还不是很熟悉。如果是这样,请接着往下看。

postgresql类型

下面的类型引用字菜鸟教程,点击链接可以跳转

我们可以对比着mysql来简单了解学习postgresql。如果对mysql比较数据,那么学习postgresql也会比较容易。相比较与mysqlpostgresql有着更丰富的数据类型,更高的安全性。
postgresql类型如下:

数值类型

数值类型由 2 字节、4 字节或 8 字节的整数以及 4 字节或 8 字节的浮点数和可选精度的十进制数组成。

下表列出了可用的数值类型

名字 存储长度 描述 范围
smallint 2 字节 小范围整数范围 -32768 到 +32767
integer 4 字节 常用的整数 -2147483648 到 +2147483647
bigint 8 字节 大范围整数 -9223372036854775808 到 +9223372036854775807
decimal 可变长 用户指定的精度,精确 小数点前 131072 位;小数点后 16383 位
numeric 可变长 用户指定的精度,精确 小数点前 131072 位;小数点后 16383 位
real 4 字节 可变精度,不精确 6 位十进制数字精度
double precision 8 字节 可变精度,不精确 15 位十进制数字精度
smallserial 2 字节 自增的小范围整数 1 到 32767
serial 4 字节 自增整数 1 到 2147483647
bigserial 8 字节 自增的大范围整数 1 到 9223372036854775807

货币类型

money 类型存储带有固定小数精度的货币金额。

numeric、int 和 bigint 类型的值可以转换为 money,不建议使用浮点数来处理处理货币类型,因为存在舍入错误的可能性。

名字 存储长度 描述 范围
money 8 字节 货币金额 -92233720368547758.08 到 +92233720368547758.07

字符类型

下表列出了 PostgreSQL 所支持的字符类型:

序号 名字 & 描述
1 character varying(n), varchar(n)变长,有长度限制
2 character(n), char(n) f定长,不足补空白
3 text 变长,无长度限制

日期/时间类型

下表列出了 PostgreSQL 支持的日期和时间类型。

名字 存储空间 描述 最低值 最高值 分辨率
timestamp [ § ] [ without time zone ] 8 字节 日期和时间(无时区) 4713 BC 294276 AD 1 毫秒 / 14 位
timestamp [ § ] with time zone 8 字节 日期和时间,有时区 4713 BC 294276 AD 1 毫秒 / 14 位
date 4 字节 只用于日期 4713 BC 5874897 AD 1 天
time [ § ] [ without time zone ] 8 字节 只用于一日内时间 00:00:00 24:00:00 1 毫秒 / 14 位
time [ § ] with time zone 12 字节 只用于一日内时间,带时区 00:00:00+1459 24:00:00-1459 1 毫秒 / 14 位
interval [ fields ] [ § ] 12 字节 时间间隔 -178000000 年 178000000 年 1 毫秒 / 14 位

布尔类型

PostgreSQL 支持标准的 boolean 数据类型。

boolean 有"true"(真)或"false"(假)两个状态, 第三种"unknown"(未知)状态,用 NULL 表示。

名称 存储格式 描述
boolean 1 字节 true/false

枚举类型

枚举类型是一个包含静态和值的有序集合的数据类型。

PostgreSQL 中的枚举类型类似于 C 语言中的 enum 类型。

与其他类型不同的是枚举类型需要使用 CREATE TYPE 命令创建。

sql 复制代码
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

创建一周中的几天,如下所示:

sql 复制代码
CREATE TYPE week AS ENUM ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');

就像其他类型一样,一旦创建,枚举类型可以用于表和函数定义。

sql 复制代码
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE person (
name text,
current_mood mood
);
INSERT INTO person VALUES ('Moe', 'happy');
SELECT * FROM person WHERE current_mood = 'happy';
name | current_mood
------+--------------
Moe  | happy
(1 row)

几何类型

几何数据类型表示二维的平面物体。

下表列出了 PostgreSQL 支持的几何类型。

最基本的类型:点。它是其它类型的基础。

名字 存储空间 说明 表现形式
point 16 字节 平面中的点 (x,y)
line 32 字节 (无穷)直线(未完全实现) ((x1,y1),(x2,y2))
lseg 32 字节 (有限)线段 ((x1,y1),(x2,y2))
box 32 字节 矩形 ((x1,y1),(x2,y2))
path 16+16n 字节 闭合路径(与多边形类似) ((x1,y1),...)
path 16+16n 字节 开放路径 [(x1,y1),...]
polygon 40+16n 字节 多边形(与闭合路径相似) ((x1,y1),...)
circle 24 字节 <(x,y),r> (圆心和半径)

网络地址类型

PostgreSQL 提供用于存储 IPv4 、IPv6 、MAC 地址的数据类型。

用这些数据类型存储网络地址比用纯文本类型好, 因为这些类型提供输入错误检查和特殊的操作和功能。

名字 存储空间 描述
cidr 7 或 19 字节 IPv4 或 IPv6 网络
inet 7 或 19 字节 IPv4 或 IPv6 主机和网络
macaddr 6 字节 MAC 地址

在对 inet 或 cidr 数据类型进行排序的时候, IPv4 地址总是排在 IPv6 地址前面,包括那些封装或者是映射在 IPv6 地址里的 IPv4 地址, 比如 ::10.2.3.4 或 ::ffff:10.4.3.2。

位串类型

位串就是一串 1 和 0 的字符串。它们可以用于存储和直观化位掩码。 我们有两种 SQL 位类型:bit(n) 和bit varying(n), 这里的n是一个正整数。

bit 类型的数据必须准确匹配长度 n, 试图存储短些或者长一些的数据都是错误的。bit varying 类型数据是最长 n 的变长类型;更长的串会被拒绝。 写一个没有长度的bit 等效于 bit(1), 没有长度的 bit varying 意思是没有长度限制。

文本搜索类型

全文检索即通过自然语言文档的集合来找到那些匹配一个查询的检索。

PostgreSQL 提供了两种数据类型用于支持全文检索:

序号 名字 & 描述
1 tsvector tsvector 的值是一个无重复值的 lexemes 排序列表, 即一些同一个词的不同变种的标准化。
2 tsquery tsquery 存储用于检索的词汇,并且使用布尔操作符 &(AND),

UUID 类型

uuid 数据类型用来存储 RFC 4122,ISO/IEF 9834-8:2005 以及相关标准定义的通用唯一标识符(UUID)。 (一些系统认为这个数据类型为全球唯一标识符,或GUID。) 这个标识符是一个由算法产生的 128 位标识符,使它不可能在已知使用相同算法的模块中和其他方式产生的标识符相同。 因此,对分布式系统而言,这种标识符比序列能更好的提供唯一性保证,因为序列只能在单一数据库中保证唯一。

UUID 被写成一个小写十六进制数字的序列,由分字符分成几组, 特别是一组8位数字+3组4位数字+一组12位数字,总共 32 个数字代表 128 位, 一个这种标准的 UUID 例子如下:

sql 复制代码
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

XML 类型

xml 数据类型可以用于存储XML数据。 将 XML 数据存到 text 类型中的优势在于它能够为结构良好性来检查输入值, 并且还支持函数对其进行类型安全性检查。 要使用这个数据类型,编译时必须使用 configure --with-libxml。

xml 可以存储由XML标准定义的格式良好的"文档", 以及由 XML 标准中的 XMLDecl? content 定义的"内容"片段, 大致上,这意味着内容片段可以有多个顶级元素或字符节点。 xmlvalue IS DOCUMENT 表达式可以用来判断一个特定的 xml 值是一个完整的文件还是内容片段。

创建XML值

使用函数 xmlparse: 来从字符数据产生 xml 类型的值:

sql 复制代码
XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Manual</title><chapter>...</chapter></book>')
XMLPARSE (CONTENT 'abc<foo>bar</foo><bar>foo</bar>')

JSON 类型

json 数据类型可以用来存储 JSON(JavaScript Object Notation)数据, 这样的数据也可以存储为 text,但是 json 数据类型更有利于检查每个存储的数值是可用的 JSON 值。

此外还有相关的函数来处理 json 数据:

实例 实例结果
array_to_json('{{1,5},{99,100}}'::int[]) [[1,5],[99,100]]
row_to_json(row(1,'foo')) {"f1":1,"f2":"foo"}

数组类型

PostgreSQL 允许将字段定义成变长的多维数组。

数组类型可以是任何基本类型或用户定义类型,枚举类型或复合类型。

声明数组

创建表的时候,我们可以声明数组,方式如下:

sql 复制代码
CREATE TABLE sal_emp (
name            text,
pay_by_quarter  integer[],
schedule        text[][]
);

实际使用postgresql

使用postgresql肯定是根据postgresql的类型进行使用,postgresql的类型定义如上,下面我们开始在代码中定义使用这些类型。
demo使用技术: postgresql,mybatis

本篇demo的演示借助于mybatis框架实现。

1.首先梳理一下,如果要使用一个数据库,那么必须要先连接到数据库,即配置数据源。

2.连接数据库成功后就需要操作数据,写入数据到数据库,从数据库取出数据。

因为postgresql有丰富的数据类型,不同的数据类型有不同的处理器。基于此,mybatis提供了类型处理器来对不同的类型进行处理。

使用mybatis时必须有两个配置文件

mybatis的核心配置文件:配置数据源相关信息

数据库操作的mapper文件:sql语句与查询接口的映射

mybatis配置文件

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>
<!--    给User对象起别名User,如果不定义这个,后续使用到User的地方必须引用User对象的全路径名-->
    <typeAliases>
        <typeAlias type="com.example.pgstudy.User" alias="User"/>
    </typeAliases>
<!--    全局类型处理器-->
    <typeHandlers>
        <typeHandler handler="com.example.pgstudy.UUIDTypeHandler"/>
        <typeHandler handler="com.example.pgstudy.IntegerArrayTypeHandler"/>
        <typeHandler handler="com.example.pgstudy.StringArrayTypeHandler"/>
    </typeHandlers>
<!--    配置数据源-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.postgresql.Driver"/>
                <property name="url" value="jdbc:postgresql://localhost:5432/root"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
<!--    配置mapper资源文件-->
    <mappers>
    	<mapper resource="UserMapper.xml"/>
        <mapper resource="DemoTypeMapper.xml"/>
    </mappers>
</configuration>

DemoTypeMapper.xml

xml 复制代码
<?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="com.example.pgstudy.DemoTypeMapper">
    <resultMap id="DemoTypeResultMap" type="com.example.pgstudy.DemoType">
        <id property="id" column="id" />
        <result property="intCol" column="int_col" />
        <result property="bigintCol" column="bigint_col" />
        <result property="smallintCol" column="smallint_col" />
        <result property="serialCol" column="serial_col" />
        <result property="decimalCol" column="decimal_col" />
        <result property="numericCol" column="numeric_col" />
        <result property="realCol" column="real_col" />
        <result property="doubleCol" column="double_col" />
        <result property="charCol" column="char_col" />
        <result property="varcharCol" column="varchar_col" />
        <result property="textCol" column="text_col" />
        <result property="boolCol" column="bool_col" />
        <result property="dateCol" column="date_col" />
        <result property="timeCol" column="time_col" />
        <result property="timestampCol" column="timestamp_col" />
        <result property="intervalCol" column="interval_col" typeHandler="com.example.pgstudy.IntervalTypeHandler" />
        <result property="byteaCol" column="bytea_col" />
        <result property="jsonCol" column="json_col" typeHandler="com.example.pgstudy.JsonTypeHandler" />
        <result property="jsonbCol" column="jsonb_col" typeHandler="com.example.pgstudy.JsonbTypeHandler" />
        <result property="uuidCol" column="uuid_col" />
        <result property="intArrayCol" column="int_array_col" />
        <result property="textArrayCol" column="text_array_col" />
        <result property="inetCol" column="inet_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="cidrCol" column="cidr_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="macaddrCol" column="macaddr_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="pointCol" column="point_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="lineCol" column="line_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="lsegCol" column="lseg_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="boxCol" column="box_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="pathCol" column="path_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="polygonCol" column="polygon_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="circleCol" column="circle_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="xmlCol" column="xml_col" typeHandler="com.example.pgstudy.PostgreSQLTypeHandler" />
        <result property="moneyCol" column="money_col" />
    </resultMap>

    <insert id="insertDemoType" parameterType="com.example.pgstudy.DemoType">
        INSERT INTO demo_types (
            int_col, bigint_col, smallint_col, serial_col, decimal_col, numeric_col, real_col, double_col,
            char_col, varchar_col, text_col, bool_col, date_col, time_col, timestamp_col, interval_col,
            bytea_col, json_col, jsonb_col, uuid_col, int_array_col, text_array_col, inet_col, cidr_col, macaddr_col,
            point_col, line_col, lseg_col, box_col, path_col, polygon_col, circle_col, xml_col, money_col
        ) VALUES (
            #{intCol}, #{bigintCol}, #{smallintCol}, #{serialCol}, #{decimalCol}, #{numericCol}, #{realCol}, #{doubleCol},
            #{charCol}, #{varcharCol}, #{textCol}, #{boolCol}, #{dateCol}, #{timeCol}, #{timestampCol}, #{intervalCol, typeHandler=com.example.pgstudy.IntervalTypeHandler},
            #{byteaCol}, #{jsonCol, typeHandler=com.example.pgstudy.JsonTypeHandler}, #{jsonbCol, typeHandler=com.example.pgstudy.JsonbTypeHandler}, #{uuidCol}, #{intArrayCol, jdbcType=ARRAY}, #{textArrayCol, jdbcType=ARRAY}, 
            #{inetCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{cidrCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{macaddrCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler},
            #{pointCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{lineCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{lsegCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{boxCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{pathCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{polygonCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{circleCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{xmlCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, #{moneyCol}
        )
    </insert>

    <select id="selectAllDemoTypes" resultMap="DemoTypeResultMap">
        SELECT * FROM demo_types
    </select>

    <update id="updateDemoType" parameterType="com.example.pgstudy.DemoType">
        UPDATE demo_types SET
            int_col=#{intCol}, bigint_col=#{bigintCol}, smallint_col=#{smallintCol}, serial_col=#{serialCol},
            decimal_col=#{decimalCol}, numeric_col=#{numericCol}, real_col=#{realCol}, double_col=#{doubleCol},
            char_col=#{charCol}, varchar_col=#{varcharCol}, text_col=#{textCol}, bool_col=#{boolCol},
            date_col=#{dateCol}, time_col=#{timeCol}, timestamp_col=#{timestampCol}, interval_col=#{intervalCol, typeHandler=com.example.pgstudy.IntervalTypeHandler},
            bytea_col=#{byteaCol}, json_col=#{jsonCol, typeHandler=com.example.pgstudy.JsonTypeHandler}, jsonb_col=#{jsonbCol, typeHandler=com.example.pgstudy.JsonbTypeHandler}, uuid_col=#{uuidCol},
            int_array_col=#{intArrayCol, jdbcType=ARRAY}, text_array_col=#{textArrayCol, jdbcType=ARRAY},
            inet_col=#{inetCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, cidr_col=#{cidrCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, macaddr_col=#{macaddrCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, point_col=#{pointCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler},
            line_col=#{lineCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, lseg_col=#{lsegCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, box_col=#{boxCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, path_col=#{pathCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler},
            polygon_col=#{polygonCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, circle_col=#{circleCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, xml_col=#{xmlCol, typeHandler=com.example.pgstudy.PostgreSQLTypeHandler}, money_col=#{moneyCol}
        WHERE id=#{id}
    </update>

    <delete id="deleteDemoTypeById" parameterType="int">
        DELETE FROM demo_types WHERE id=#{id}
    </delete>
</mapper> 

UserMapper.xml(不是必须的)

xml 复制代码
<?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="com.example.pgstudy.UserMapper">
    <resultMap id="UserResultMap" type="com.example.pgstudy.User">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="age" column="age" />
    </resultMap>

    <insert id="insertUser" parameterType="com.example.pgstudy.User">
        INSERT INTO users (name, age) VALUES (#{name}, #{age})
    </insert>

    <select id="selectAllUsers" resultMap="UserResultMap">
        SELECT * FROM users
    </select>

    <update id="updateUser" parameterType="com.example.pgstudy.User">
        UPDATE users SET age = #{age} WHERE name = #{name}
    </update>

    <delete id="deleteUserByName" parameterType="string">
        DELETE FROM users WHERE name = #{name}
    </delete>
</mapper> 

DemoTypeMapper

java 复制代码
package com.example.pgstudy;

import java.util.List;

public interface DemoTypeMapper {
    void insertDemoType(DemoType demoType);
    List<DemoType> selectAllDemoTypes();
    void updateDemoType(DemoType demoType);
    void deleteDemoTypeById(Integer id);
} 

UserMapper

java 复制代码
package com.example.pgstudy;

import java.util.List;

public interface UserMapper {
    void insertUser(User user);
    List<User> selectAllUsers();
    void updateUser(User user);
    void deleteUserByName(String name);
} 

PostgreSQLTypeHandler

java 复制代码
package com.example.pgstudy;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.postgresql.util.PGobject;

import java.sql.*;

@MappedTypes(String.class)
public class PostgreSQLTypeHandler extends BaseTypeHandler<String> {
    
    private String pgTypeName;
    
    public PostgreSQLTypeHandler() {
        // 默认构造函数
    }
    
    public PostgreSQLTypeHandler(String pgTypeName) {
        this.pgTypeName = pgTypeName;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        PGobject pgObject = new PGobject();
        
        // 根据参数自动识别类型或使用指定类型
        if (pgTypeName != null) {
            pgObject.setType(pgTypeName);
        } else {
            // 改进的自动识别逻辑
            if (parameter.matches("\\d+\\.\\d+\\.\\d+\\.\\d+/\\d+")) {
                pgObject.setType("cidr");  
            } else if (parameter.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
                pgObject.setType("inet");
            } else if (parameter.matches("[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}")) {
                pgObject.setType("macaddr");
            } else if (parameter.startsWith("(") && parameter.endsWith(")") && parameter.contains(",") && !parameter.contains("),(")) {
                pgObject.setType("point");
            } else if (parameter.startsWith("{") && parameter.endsWith("}") && parameter.contains(",")) {
                pgObject.setType("line");
            } else if (parameter.startsWith("[(") && parameter.endsWith(")]")) {
                pgObject.setType("lseg");
            } else if (parameter.startsWith("((") && parameter.endsWith("))")) {
                // 区分 box, path, polygon
                String content = parameter.substring(2, parameter.length() - 2);
                String[] parts = content.split("\\),\\(");
                if (parts.length == 2) {
                    pgObject.setType("box");  // box has exactly 2 points
                } else if (parts.length > 2) {
                    // 假设超过2个点的是path,闭合的多边形用不同格式
                    pgObject.setType("path");
                } else {
                    pgObject.setType("box");  // fallback
                }
            } else if (parameter.matches("\\([0-9\\.\\-]+,[0-9\\.\\-]+\\),\\([0-9\\.\\-]+,[0-9\\.\\-]+\\)")) {
                // PostgreSQL box类型的输出格式: (x1,y1),(x2,y2)
                pgObject.setType("box");
            } else if (parameter.startsWith("<(") && parameter.endsWith(">")) {
                pgObject.setType("circle");
            } else if (isXmlFormat(parameter)) {
                pgObject.setType("xml");
            } else {
                // 为了安全起见,让PostgreSQL自己处理类型转换
                pgObject.setType("text");
            }
        }
        
        pgObject.setValue(parameter);
        ps.setObject(i, pgObject);
    }
    
    /**
     * 判断字符串是否为XML格式
     */
    private boolean isXmlFormat(String str) {
        if (str == null || str.trim().isEmpty()) {
            return false;
        }
        
        String trimmed = str.trim();
        // 基本XML格式检查:以<开始,以>结束,包含标签
        if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
            // 进一步检查是否包含XML标签结构
            if (trimmed.contains("</") || trimmed.startsWith("<?xml") || 
                (trimmed.indexOf('>') > 0 && trimmed.indexOf('>') < trimmed.length() - 1)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object obj = rs.getObject(columnName);
        return convertToString(obj);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object obj = rs.getObject(columnIndex);
        return convertToString(obj);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object obj = cs.getObject(columnIndex);
        return convertToString(obj);
    }
    
    /**
     * 将数据库对象转换为字符串,特别处理XML类型
     */
    private String convertToString(Object obj) throws SQLException {
        if (obj == null) {
            return null;
        }
        
        // 检查是否是PostgreSQL XML类型
        if (obj instanceof SQLXML) {
            SQLXML xmlObj = (SQLXML) obj;
            return xmlObj.getString();
        }
        
        return obj.toString();
    }
} 

IntegerArrayTypeHandler

java 复制代码
package com.example.pgstudy;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.postgresql.util.PGobject;

import java.sql.*;

@MappedTypes(Integer[].class)
public class IntegerArrayTypeHandler extends BaseTypeHandler<Integer[]> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Integer[] parameter, JdbcType jdbcType) throws SQLException {
        StringBuilder str = new StringBuilder();
        str.append("{");
        for (int j = 0; j < parameter.length; j++) {
            str.append(parameter[j]);
            if (j < parameter.length - 1) {
                str.append(",");
            }
        }
        str.append("}");
        
        PGobject pgObject = new PGobject();
        pgObject.setType("int4[]");
        pgObject.setValue(str.toString());
        ps.setObject(i, pgObject);
    }

    @Override
    public Integer[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Array array = rs.getArray(columnName);
        if (array == null) {
            return null;
        }
        return (Integer[]) array.getArray();
    }

    @Override
    public Integer[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Array array = rs.getArray(columnIndex);
        if (array == null) {
            return null;
        }
        return (Integer[]) array.getArray();
    }

    @Override
    public Integer[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Array array = cs.getArray(columnIndex);
        if (array == null) {
            return null;
        }
        return (Integer[]) array.getArray();
    }
} 

目录结构

DemoType类

java 复制代码
package com.example.pgstudy;

import lombok.Data;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Duration;
import java.util.UUID;

@Data
public class DemoType {
    private Integer id;
    private Integer intCol;
    private Long bigintCol;
    private Short smallintCol;
    private Integer serialCol;
    private BigDecimal decimalCol;
    private BigDecimal numericCol;
    private Float realCol;
    private Double doubleCol;
    private String charCol;
    private String varcharCol;
    private String textCol;
    private Boolean boolCol;
    private Date dateCol;
    private Time timeCol;
    private Timestamp timestampCol;
    private String intervalCol; // interval建议用String或PGInterval
    private byte[] byteaCol;
    private String jsonCol;
    private String jsonbCol;
    private UUID uuidCol;
    private Integer[] intArrayCol;
    private String[] textArrayCol;
    private String inetCol;
    private String cidrCol;
    private String macaddrCol;
    private String pointCol;
    private String lineCol;
    private String lsegCol;
    private String boxCol;
    private String pathCol;
    private String polygonCol;
    private String circleCol;
    private String xmlCol;
    private BigDecimal moneyCol;
} 

执行入口

java 复制代码
package com.example.pgstudy;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException, SQLException {
        initTable();

        // MyBatis配置
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        try (SqlSession session = sqlSessionFactory.openSession(true)) { // 自动提交
            UserMapper mapper = session.getMapper(UserMapper.class);

            // 插入数据
            User user1 = new User();
            user1.setName("张三");
            user1.setAge(20);
            mapper.insertUser(user1);
            System.out.println("插入成功");

            // 查询数据
            List<User> users = mapper.selectAllUsers();
            System.out.println("查询结果:");
            for (User u : users) {
                System.out.println("ID: " + u.getId() + ", Name: " + u.getName() + ", Age: " + u.getAge());
            }

            // 更新数据
            user1.setAge(25);
            mapper.updateUser(user1);
            System.out.println("更新成功");

            // 删除数据
            mapper.deleteUserByName("张三");
            System.out.println("删除成功");

            // 测试所有类型的插入、查询、更新、删除
            DemoTypeMapper demoTypeMapper = session.getMapper(DemoTypeMapper.class);
            DemoType demo = new DemoType();
            demo.setIntCol(1);
            demo.setBigintCol(1234567890123L);
            demo.setSmallintCol((short)2);
            demo.setSerialCol(10);
            demo.setDecimalCol(new java.math.BigDecimal("123.45"));
            demo.setNumericCol(new java.math.BigDecimal("678.90"));
            demo.setRealCol(3.14f);
            demo.setDoubleCol(2.718);
            demo.setCharCol("abc");
            demo.setVarcharCol("hello world");
            demo.setTextCol("这是一段文本");
            demo.setBoolCol(true);
            demo.setDateCol(java.sql.Date.valueOf("2024-06-01"));
            demo.setTimeCol(java.sql.Time.valueOf("12:34:56"));
            demo.setTimestampCol(java.sql.Timestamp.valueOf("2024-06-01 12:34:56"));
            demo.setIntervalCol("1 day 2 hours");
            demo.setByteaCol("hello bytea".getBytes());
            demo.setJsonCol("{\"a\":1,\"b\":2}");
            demo.setJsonbCol("{\"x\":true}");
            demo.setUuidCol(java.util.UUID.randomUUID());
            demo.setIntArrayCol(new Integer[]{1,2,3});
            demo.setTextArrayCol(new String[]{"a","b","c"});
            demo.setInetCol("192.168.1.1");
            demo.setCidrCol("192.168.100.128/25");
            demo.setMacaddrCol("08:00:2b:01:02:03");
            demo.setPointCol("(1,2)");
            demo.setLineCol("{1,2,3}");
            demo.setLsegCol("[(0,0),(1,1)]");
            demo.setBoxCol("((0,0),(1,1))");
            demo.setPathCol("((1,1),(2,2),(3,3))");
            demo.setPolygonCol("((0,0),(1,1),(1,0))");
            demo.setCircleCol("<(0,0),2>");
            demo.setXmlCol("<root><a>1</a></root>");
            demo.setMoneyCol(new java.math.BigDecimal("100.50"));
            demoTypeMapper.insertDemoType(demo);
            System.out.println("插入demo_types成功");

            // 查询
            var demoList = demoTypeMapper.selectAllDemoTypes();
            System.out.println("查询demo_types结果:");
            for (DemoType d : demoList) {
                System.out.println("ID: " + d.getId() + ", intCol: " + d.getIntCol() + ", jsonCol: " + d.getJsonCol());
                System.out.println("box_col: '" + d.getBoxCol() + "', path_col: '" + d.getPathCol() + "'");
                System.out.println("xml_col: '" + d.getXmlCol() + "'");
            }

            // 更新
            if (!demoList.isEmpty()) {
                DemoType first = demoList.get(0);
                first.setTextCol("更新后的文本");
                System.out.println("准备更新,box_col值为: '" + first.getBoxCol() + "'");
                demoTypeMapper.updateDemoType(first);
                System.out.println("更新demo_types成功");
            }

            // 删除
            if (!demoList.isEmpty()) {
                demoTypeMapper.deleteDemoTypeById(demoList.get(0).getId());
                System.out.println("删除demo_types成功");
            }
        }
    }

    public static void initTable() throws IOException, SQLException {

        // 1. 先确保表存在
        String url = "jdbc:postgresql://localhost:5432/root";
        String user = "root";
        String password = "123456";
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            String createTable = "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(100), age INT)";
            try (Statement stmt = conn.createStatement()) {
                stmt.execute(createTable);
            }
        }

        // 创建demo_types表(如已存在可忽略)
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            String createDemoTypesTable = """
            CREATE TABLE IF NOT EXISTS demo_types (
                id SERIAL PRIMARY KEY,
                int_col INTEGER,
                bigint_col BIGINT,
                smallint_col SMALLINT,
                serial_col SERIAL,
                decimal_col DECIMAL(10,2),
                numeric_col NUMERIC(10,2),
                real_col REAL,
                double_col DOUBLE PRECISION,
                char_col CHAR(10),
                varchar_col VARCHAR(50),
                text_col TEXT,
                bool_col BOOLEAN,
                date_col DATE,
                time_col TIME,
                timestamp_col TIMESTAMP,
                interval_col INTERVAL,
                bytea_col BYTEA,
                json_col JSON,
                jsonb_col JSONB,
                uuid_col UUID,
                int_array_col INTEGER[],
                text_array_col TEXT[],
                inet_col INET,
                cidr_col CIDR,
                macaddr_col MACADDR,
                point_col POINT,
                line_col LINE,
                lseg_col LSEG,
                box_col BOX,
                path_col PATH,
                polygon_col POLYGON,
                circle_col CIRCLE,
                xml_col XML,
                money_col MONEY
            );
            """;
            try (Statement stmt = conn.createStatement()) {
                stmt.execute(createDemoTypesTable);
            }
        }
    }
} 

执行流程分析

1.使用initTable()初始化创建所需要的表。

2.利用mybatis执行数据增删改查操作。

我们在上面展示代码中只挑选出了两个比较有代表性的TypeHandler,分别是IntegerArrayTypeHandler,PostgreSQLTypeHandler,其余的类型处理器逻辑都基本相同。其中IntegerArrayTypeHandler属于操作数组类型,PostgreSQLTypeHandler属于操作丰富的postgresql类型。

如果是实现自己的类型处理器,就需要集成BaseTypeHandler<T>类。其中,T表示要设置的参数类型。对于IntegerArrayTypeHandler处理器来说,T表示Integer[];对于PostgreSQLTypeHandler处理器来说,T表示String。实现该处理器之后,重写对应的setXXX/getXXX方法即可。
setXXX表示往数据库写入数据,getXXX表示从数据库中读取数据。

如上图所示,当我们实现好对应的处理器,并把处理器放在mybatis核心配置文件/mapper文件中后,就可以调用mapper的接口进行操作数据库了。
demo.setIntArrayCol表示设置数组数据。
demo.setInetCol、demo.setCidrCol等方法表示设置postgresql的其他类型数据。可以发现对于postgresql内置的好多复杂类型,基本都是通过String类型传值设置。

我们此时可能有两个疑问:

  • 问题一:为什么对于postgersql内置的类型是通过String类型值传入的?
  • 问题二:对于代码中使用string值与postgresql的内置类型对应,这种操作有什么优势?
  • 问题三:为什么在mybatis核心配置文件,或者是mapper文件中配置类处理器都可以?

下面我们一一解答上面三个问题。

问题

问题一:为什么对于postgersql内置的类型是通过String类型值传入的?

首先,我们应该知道java代码和postgresql是对应两种不同的语言。即一个是编程语言,一个是sql语言。对于两个不同的语言,其表示的类型肯定有所差异,也有共同点,共同点就是都有字符串,差异就不用讲了,因为两种不同的语言,差异太多了。

不管是什么语言,都有字符串,所以是不是可以理解为字符串是一种适配器?通过字符串可以把不同的语言都给联系起来,进行信息/数据交换。

问题二:对于代码中使用string值与postgresql的内置类型对应,这种操作有什么优势?

我们可以发现一个有意思的点,对于PostgreSQLTypeHandler的类型参数都为string类型,这个处理器的核心只是设置pgTypeName,即参数类型。那我们此时可能有个疑问,代码中都定义string类型表示数据,那在代码层面上postgresql丰富的数据类型貌似并没有什么优势,那postgresql的优势在哪?

优势主要包括以下几点:

  • 数据完整性: 自动验证和强制类型约束
  • 性能优化: 专门的存储格式和索引支持
  • 功能丰富: 特定类型的操作符和函数
  • 开发效率: 减少应用层验证代码
  • 维护性: 统一的数据处理标准

1.数据完整性验证

sql 复制代码
-- 使用VARCHAR
INSERT INTO table1 (ip_address) VALUES ('256.256.256.256'); -- 错误的IP地址也能插入
-- 使用INET
INSERT INTO table1 (ip_address) VALUES ('256.256.256.256'); -- PostgreSQL直接报错,拒绝插入

2.索引和查询优化

sql 复制代码
-- INET类型支持网段查询
SELECT * FROM networks WHERE ip_address << '192.168.1.0/24';

-- JSONB支持特定索引
CREATE INDEX idx_json ON table1 USING GIN (json_data);

-- 几何类型支持空间查询
SELECT * FROM locations WHERE point_col <-> POINT(0,0) < 10;

3.特殊操作和函数支持
网络类型

sql 复制代码
-- PostgreSQL INET类型
SELECT ip_address & '255.255.255.0'::inet;  -- 网络掩码运算
SELECT ip_address << '192.168.1.0/24'::inet;  -- 子网判断

-- VARCHAR类型
-- 需要复杂的字符串处理和自定义函数

几何类型

sql 复制代码
-- PostgreSQL几何类型
SELECT box_col @> point '(1,1)';  -- 点是否在框内
SELECT circle_col && box_col;     -- 圆和框是否相交

-- VARCHAR类型
-- 需要解析字符串并编写复杂的数学计算

JSON/JSONB类型

sql 复制代码
-- PostgreSQL JSONB
SELECT json_data->>'name' FROM table1;  -- JSON字段提取
SELECT * FROM table1 WHERE json_data @> '{"status": "active"}';  -- JSON包含查询

-- VARCHAR类型
-- 需要手动解析JSON字符串,无法使用索引优化

4.数据一致性和业务规则
类型约束

sql 复制代码
-- PostgreSQL特定类型
CREATE TABLE networks (
    ip INET,
    subnet CIDR,
    mac MACADDR
);

-- 自动强制类型检查:
INSERT INTO networks VALUES ('invalid-ip', 'invalid-subnet', 'invalid-mac'); -- 直接报错

-- VARCHAR类型
CREATE TABLE networks (
    ip VARCHAR(15),
    subnet VARCHAR(18),
    mac VARCHAR(17)
);
-- 需要在应用层编写验证逻辑

问题三:为什么在mybatis核心配置文件,或者是mapper文件中配置类处理器都可以?

这个问题很好理解,原因就是如果在mybatis和核心配置文件里配置类处理器,那么对于该处理器在定义时会定义注解:
@MappedTypes(Integer[].class),即只要遇到类型Integer[],就会应用该处理器,即在mybatis核心配置文件中定义的类型处理器全局生效,只要遇到符合该处理器定义的类型时就会应用该处理器。

如果是在mapper文件中定义的处理器,即只会针对当前字段应用该处理器,可以更精细化的控制。如果在mybatis核心配置文件中配置PostgreSQLTypeHandler处理器,因为该处理器处理的类型为String,那么当遇到String类型时就会应用该处理器,可以对于String类型的json串,需要应用JsonTypeHandler,所以如果定义在mybatis的核心配置文件中就会有冲突,因此对于可能会冲突的类型处理器,定义在mapper文件中更合理。

源码git仓库地址

相关推荐
小云数据库服务专线21 分钟前
GaussDB数据库架构师修炼(十六) 如何选择磁盘
数据库·数据库架构·gaussdb
码出财富1 小时前
SQL语法大全指南
数据库·mysql·oracle
异世界贤狼转生码农3 小时前
MongoDB Windows 系统实战手册:从配置到数据处理入门
数据库·mongodb
QuZhengRong3 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
码农阿豪3 小时前
Windows从零到一安装KingbaseES数据库及使用ksql工具连接全指南
数据库·windows
时序数据说9 小时前
时序数据库市场前景分析
大数据·数据库·物联网·开源·时序数据库
听雪楼主.12 小时前
Oracle Undo Tablespace 使用率暴涨案例分析
数据库·oracle·架构
我科绝伦(Huanhuan Zhou)12 小时前
KINGBASE集群日常维护管理命令总结
数据库·database
妖灵翎幺13 小时前
Java应届生求职八股(2)---Mysql篇
数据库·mysql
HMBBLOVEPDX13 小时前
MySQL的事务日志:
数据库·mysql