知乎专栏 |
ID 字段,数据库中的主键。
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false) private int id;
@GeneratedValue 主键生成策略:
@GeneratedValue(strategy= GenerationType.IDENTITY) 该注解由数据库自动生成,AUTO_INCREMENT 自增主键,在 mysql 数据库中使用最频繁,oracle 不支持。 @GeneratedValue(strategy= GenerationType.AUTO) 主键由程序控制,默认的主键生成策略,oracle 默认是序列化的方式,mysql 默认是主键自增的方式。 @GeneratedValue(strategy= GenerationType.SEQUENCE) 根据底层数据库的序列来生成主键,条件是数据库支持序列,Oracle支持,Mysql不支持。 @GeneratedValue(strategy= GenerationType.TABLE) 使用一个特定的数据库表格来保存主键,较少使用。
Long = bigint
package cn.netkiller.domain; import jakarta.persistence.*; import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; @Entity @Table @Data public class Picture implements Serializable { @Serial public static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false) private Long id; private String device; private String model; private String session; private String prompt; private String thumbnail; private String image; private String story; private boolean share; private int likes; private int favorites; private int forward; private Date ctime; private Date mtime; }
CREATE TABLE `picture` ( `id` bigint NOT NULL AUTO_INCREMENT, `ctime` datetime(6) DEFAULT NULL, `favorites` int NOT NULL, `image` varchar(255) DEFAULT NULL, `likes` int NOT NULL, `mtime` datetime(6) DEFAULT NULL, `prompt` varchar(255) DEFAULT NULL, `session` varchar(255) DEFAULT NULL, `share` bit(1) NOT NULL, `story` varchar(255) DEFAULT NULL, `thumbnail` varchar(255) DEFAULT NULL, `device` varchar(255) DEFAULT NULL, `model` varchar(255) DEFAULT NULL, `forward` int NOT NULL PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
字符串做主键
package api.domain; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table() public class TransactionsPostion { @Id private String address; private String startblock; private String endblock; public TransactionsPostion() { // TODO Auto-generated constructor stub } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getStartblock() { return startblock; } public void setStartblock(String startblock) { this.startblock = startblock; } public String getEndblock() { return endblock; } public void setEndblock(String endblock) { this.endblock = endblock; } }
对应数据库表
CREATE TABLE "transactions_postion" ( "address" varchar(255) NOT NULL, "endblock" varchar(255) DEFAULT NULL, "startblock" varchar(255) DEFAULT NULL, PRIMARY KEY ("address") ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
unique 属性表示该字段是否为唯一标识,默认为false。如果表中有一个字段需要唯一标识,则既可以使用该标记,也可以使用@Table标记中的@UniqueConstraint。 nullable 属性表示该字段是否可以为null值,默认为true。 insertable 属性表示在使用“INSERT”脚本插入数据时,是否需要插入该字段的值。 updatable 属性表示在使用“UPDATE”脚本插入数据时,是否需要更新该字段的值。insertable和updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的。 columnDefinition属性表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时使用。 table 属性表示当映射多个表时,指定表的表中的字段。默认值为主表的表名。 length 属性表示字段的长度,当字段的类型为varchar时,该属性才有效,默认为255个字符。 precision 属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。 scale int 列的精度,仅对十进制数值有效,表示小数位的总位数。默认为0。
@Column(precision=18, scale=5) private BigDecimal principal; @Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
private String subject; @Column(columnDefinition = "TEXT") private String content;
无符号整形
package com.example.api.domain.elasticsearch; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table public class Member { @Id private int id; @Column(columnDefinition = "INT(10) UNSIGNED NOT NULL") private int age; @Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private Date ctime; @Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP") private Date mtime; @Column(columnDefinition = "enum('Y','N') DEFAULT 'N'") private boolean status; }
CREATE TABLE `member` ( `id` int(11) NOT NULL, `age` int(10) unsigned NOT NULL, `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `status` enum('Y','N') DEFAULT 'N', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
Clob(Character Large Ojects)类型是长字符串类型,具体的java.sql.Clob, Character[], char[] 和 java.lang.String 将被持久化为 Clob 类型。
Blob(Binary Large Objects)类型是字节类型,具体的java.sql.Blob, Byte[], byte[] 和 serializable type 将被持久化为 Blob 类型。
@Lob 持久化为Blob或者Clob类型,根据get方法的返回值不同,自动进行Clob和Blob的转换。
因为这两种类型的数据一般占用的内存空间比较大,所以通常使用延迟加载的方式,与@Basic标记同时使用,设置加载方式为FetchType.LAZY。
@Lob @Basic(fetch = FetchType.LAZY) @Column(name="content", columnDefinition="CLOB", nullable=true) public String getContent() { return content; }
@Entity public class Article { @Id @GeneratedValue Integer id; @Temporal(TemporalType.DATE) Date publicationDate; @Temporal(TemporalType.TIME) Date publicationTime; @Temporal(TemporalType.TIMESTAMP) Date creationDateTime; }
@Column(name = "create_at") @CreatedDate private Timestamp create_date;
Spring 提供了 import org.springframework.data.annotation.CreatedDate;
但是这些只能作用于实体类。
@CreatedDate private Date createdDateTime;
@Column(insertable = false) @org.hibernate.annotations.ColumnDefault("1.00") @org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.INSERT ) protected Date lastModified;
@Temporal(TemporalType.TIMESTAMP) @Column(updatable = false) @org.hibernate.annotations.CreationTimestamp protected Date createdDate;
@Column(name="update_time") @org.hibernate.annotations.UpdateTimestamp @Temporal(TemporalType.TIMESTAMP) private Date updateTime;
@Temporal(TemporalType.TIMESTAMP) @Column(insertable = false, updatable = false) @org.hibernate.annotations.Generated( org.hibernate.annotations.GenerationTime.ALWAYS )
package cn.netkiller.api.domain.elasticsearch; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table public class ElasticsearchTrash { @Id private int id; @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private Date ctime; public int getId() { return id; } public void setId(int id) { this.id = id; } public Date getCtime() { return ctime; } public void setCtime(Date ctime) { this.ctime = ctime; } }
对应数据库DDL
CREATE TABLE `elasticsearch_trash` ( `id` int(11) NOT NULL, `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
需求是这样的:
1. 创建时间与更新时间只能由数据库产生,不允许在实体类中产生,因为每个节点的时间/时区不一定一直。另外防止人为插入自定义时间时间。
2. 插入记录的时候创建默认时间,创建时间不能为空,时间一旦插入不允许日后在实体类中修改。
3. 记录创建后更新日志字段为默认为 null 表示该记录没有被修改过。一旦数据被修改,修改日期字段将记录下最后的修改时间。
4. 甚至你可以通过触发器实现一个history 表,用来记录数据的历史修改,详细请参考作者另一部电子书《Netkiller Architect 手札》数据库设计相关章节。
package cn.netkiller.api.domain.elasticsearch; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.Null; @Entity @Table public class ElasticsearchTrash { @Id private int id; // 创建时间 @Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private Date ctime; // 修改时间 @Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP") private Date mtime; public int getId() { return id; } public void setId(int id) { this.id = id; } public Date getCtime() { return ctime; } public void setCtime(Date ctime) { this.ctime = ctime; } public Date getMtime() { return mtime; } public void setMtime(Date mtime) { this.mtime = mtime; } }
对应数据库DDL
CREATE TABLE `elasticsearch_trash` ( `id` int(11) NOT NULL, `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
需求:记录最后一次修改时间
package cn.netkiller.api.domain.elasticsearch; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table public class ElasticsearchTrash { @Id private int id; @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") private Date lastModified; }
产生DDL语句如下
CREATE TABLE `elasticsearch_trash` ( `id` int(11) NOT NULL, `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public java.sql.Date createdate; 创建日期 YYYY-MM-DD public java.util.Date finisheddate; 创建日期时间 YYYY-MM-DD HH:MM:SS
Json默认为 yyyy-MM-ddTHH:mm:ss 注意日期与时间中间的T,修改日期格式将T去掉
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createDate;
/** * 日期 DATE YYYY-MM-DD */ @Column(name = "create_date") @JsonFormat(shape= JsonFormat.Shape.STRING,pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8") private Date date;
下面我们实际演示一下,例如默认返回 "ctime": "2024-01-25T08:07:39.000+00:00" 这样的格式
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Thu, 25 Jan 2024 08:10:32 GMT Connection: close { "status": true, "code": "SUCCESS", "data": [ { "id": 3918, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "1.音频上传", "description": "test.amr", "ctime": "2024-01-25T08:07:16.000+00:00" }, { "id": 3919, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "2.音频转换", "description": "AMR 转 PCM", "ctime": "2024-01-25T08:07:16.000+00:00" }, { "id": 3920, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "3.语音识别", "description": "小明是个调皮的小男孩,他觉得地板上有很多污垢,于是他拿起了牙刷用它来刷地板,虽然效果不佳,但是小明却觉得是一种有趣的尝试。", "ctime": "2024-01-25T08:07:36.000+00:00" }, { "id": 3921, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "4.内容合规", "description": "合规:小明是个调皮的小男孩,他觉得地板上有很多污垢,于是他拿起了牙刷用它来刷地板,虽然效果不佳,但是小明却觉得是一种有趣的尝试。", "ctime": "2024-01-25T08:07:36.000+00:00" }, { "id": 3922, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "5.故事创作", "description": "小明,一个满腔调皮的小男孩,发现家里仿佛被污秽覆盖。他拿起牙刷,一面一面地在地板上划过,结果却不尽人意。然而,这个看似毫无预期的尝试,他自己却获得了无比的乐趣。\n\n问题:小明的行为可能使他的生活环境变得更糟糕,你会如何帮助他改变这种状况?", "ctime": "2024-01-25T08:07:36.000+00:00" }, { "id": 3923, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "6.语音合成", "description": "http://oss.test.netkiller.cn/2024/01/25/27310934-4159-4bc9-8142-67a3780faf35.mp3", "ctime": "2024-01-25T08:07:39.000+00:00" } ], "reason": "操作成功" }
加入 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 注解
package cn.netkiller.domain; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.persistence.*; import lombok.Data; import org.hibernate.annotations.Comment; import org.hibernate.annotations.DynamicUpdate; import java.io.Serial; import java.io.Serializable; import java.util.Date; @Entity @Table @DynamicUpdate @Data public class SessionStatus implements Serializable { @Serial public static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "int unsigned") @Comment("主键") private Long id; @Comment("会话主键") private String session; @Comment("进度") private String progress; @Lob @Basic(fetch = FetchType.LAZY) @Column(nullable = true, columnDefinition = "text") @Comment("描述") private String description; @Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Comment("创建时间") private Date ctime; }
日期被格式化为 "ctime": "2024-01-25 08:07:16"
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Thu, 25 Jan 2024 08:12:10 GMT Connection: close { "status": true, "code": "SUCCESS", "data": [ { "id": 3918, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "1.音频上传", "description": "test.amr", "ctime": "2024-01-25 08:07:16" }, { "id": 3919, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "2.音频转换", "description": "AMR 转 PCM", "ctime": "2024-01-25 08:07:16" }, { "id": 3920, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "3.语音识别", "description": "小明是个调皮的小男孩,他觉得地板上有很多污垢,于是他拿起了牙刷用它来刷地板,虽然效果不佳,但是小明却觉得是一种有趣的尝试。", "ctime": "2024-01-25 08:07:36" }, { "id": 3921, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "4.内容合规", "description": "合规:小明是个调皮的小男孩,他觉得地板上有很多污垢,于是他拿起了牙刷用它来刷地板,虽然效果不佳,但是小明却觉得是一种有趣的尝试。", "ctime": "2024-01-25 08:07:36" }, { "id": 3922, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "5.故事创作", "description": "小明,一个满腔调皮的小男孩,发现家里仿佛被污秽覆盖。他拿起牙刷,一面一面地在地板上划过,结果却不尽人意。然而,这个看似毫无预期的尝试,他自己却获得了无比的乐趣。\n\n问题:小明的行为可能使他的生活环境变得更糟糕,你会如何帮助他改变这种状况?", "ctime": "2024-01-25 08:07:36" }, { "id": 3923, "session": "27310934-4159-4bc9-8142-67a3780faf35", "progress": "6.语音合成", "description": "http://oss.test.netkiller.cn/2024/01/25/27310934-4159-4bc9-8142-67a3780faf35.mp3", "ctime": "2024-01-25 08:07:39" } ], "reason": "操作成功" }
Enum 枚举数据类型 MySQL 特殊数据类型
@Enumerated(EnumType.STRING) 注解可以使其成功字符串类型。
public enum StatisticsType { LIKE, COMMENT, BROWSE; } @Enumerated(EnumType.STRING) private StatisticsType type;
SQL
CREATE TABLE `statistics_history` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `member_id` bigint(20) NOT NULL, `statistics_id` bigint(20) NOT NULL, `type` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
@Enumerated(value = EnumType.ORDINAL) //ORDINAL序数
在实体中处理枚举类型适用于所有数据库,Spring data 将枚举视为 String 类型。
package cn.netkiller.api.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "statistics_history") public class StatisticsHistory implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id",unique = true, nullable = false, insertable = true, updatable = false) private long id; private long memberId; private long statisticsId; public enum StatisticsType { LIKE, COMMENT, BROWSE; } private StatisticsType type; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public long getMemberId() { return memberId; } public void setMemberId(long memberId) { this.memberId = memberId; } public long getStatisticsId() { return statisticsId; } public void setStatisticsId(long statisticsId) { this.statisticsId = statisticsId; } public StatisticsType getType() { return type; } public void setType(StatisticsType type) { this.type = type; } }
默认 enum 类型创建数据库等效 int(11)
CREATE TABLE `statistics_history` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `member_id` bigint(20) NOT NULL, `statistics_id` bigint(20) NOT NULL, `type` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; SELECT * FROM test.statistics;
在枚举中处理类型虽然可以适用于所有数据库,但有时我们希望适用数据库的枚举类型(例如MySQL),数据库中得枚举类型要比字符串效率更高
package cn.netkiller.api.domain.elasticsearch; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table public class NetkillerTrash { @Id private int id; @Column(columnDefinition = "enum('Y','N') DEFAULT 'N'") private boolean status; public int getId() { return id; } public void setId(int id) { this.id = id; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } }
实际对应的数据库DLL
CREATE TABLE `netkiller_trash` ( `id` int(11) NOT NULL, `status` enum('Y','N') DEFAULT 'N', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
public enum Gender { MALE("男士"), FEMALE("女士"); private final String value; private Gender(String value) { this.value = value; } // value 转枚举 public static Gender fromValue(String value) { for (Gender gender : values()) { if (gender.toValue().equals(value)) { return gender; } } return null; } // 枚举转 value public String toValue() { return value; } }
创建 Gender 的自定义转换器
// 实现 AttributeConverter 接口 java复制代码public class GenderConverter implements AttributeConverter<Gender, String> { @Override public String convertToDatabaseColumn(Gender gender) { return gender.toValue(); } @Override public Gender convertToEntityAttribute(String value) { return Gender.fromValue(value); } }
在实体中,枚举字段加 @Convert 注解
@Convert(converter = GenderConverter.class) @Column(name = "gender") private Gender gender;
package common.domain; import java.util.Date; import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; import common.type.OptionConverter; @Entity public class ItemPool { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false) public int id; @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @JoinColumn(name = "site_id", referencedColumnName = "id") private Site site; public String question; @Column(columnDefinition = "json DEFAULT NULL") @Convert(converter = OptionConverter.class) public Map<String, String> options; @Column(columnDefinition = "SET('A','B','C','D','E','F','G') DEFAULT NULL COMMENT '答案'") public String answer; @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @JoinColumn(name = "category_id", referencedColumnName = "id") private Category category; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'") public Date ctime; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Column(columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'") public Date mtime; }
定义 SET 如下,在JAVA中 SET被映射为逗号分隔的字符串(String),所以操作起来并无不同。使用字符串"A,B,C"存储即可,取出也同样是字符串。
@Column(columnDefinition = "SET('A','B','C','D','E','F','G') DEFAULT NULL COMMENT '答案'")
接入后查看
mysql> select answer from item_pool; +--------+ | answer | +--------+ | A,B,C | +--------+ 1 row in set (0.00 sec)
完美实现
MySQL 5.7 中增加了 json 数据类型,下面是一个例子:
CREATE TABLE `test` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `your` json DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
我们需要在 Java 实体中定义 json 数据库结构,我搜索遍了整个互联网(Google,Bing,Baidu......),没有找到解决方案,功夫不负有心人,反复尝试后终于成功。记住我是第一个这样用的 :) 。
package common.domain; import java.util.Date; import java.util.Map; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; import common.type.OptionConverter; @Entity public class ItemPool { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false) public int id; @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @JoinColumn(name = "site_id", referencedColumnName = "id") private Site site; public String name; @Column(columnDefinition = "json DEFAULT NULL") @Convert(converter = OptionConverter.class) public Map<String, String> options; @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @JoinColumn(name = "category_id", referencedColumnName = "id") private Category category; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'") public Date ctime; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Column(columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更改时间'") public Date mtime; }
类型转换 Class
package common.type; import java.util.Map; import javax.persistence.AttributeConverter; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; public class OptionConverter implements AttributeConverter<Map<String, String>, String> { Gson json = new Gson(); @Override public String convertToDatabaseColumn(Map<String, String> items) { return json.toJson(items, new TypeToken<Map<String, String>>() { }.getType()); } @Override public Map<String, String> convertToEntityAttribute(String str) { return json.fromJson(str, new TypeToken<Map<String, String>>() { }.getType()); } }
通过 @Column(columnDefinition = "json DEFAULT NULL") 定义数据库为 JSON 数据类型
数据存储与取出通过 @Convert(converter = OptionConverter.class) 做转换
这里我需要使用 Map 数据结构 public Map<String, String> options;, 你可以根据你的实际需要定义数据类型 Class
启动 Spring 项目后创建 Schema 如下:
CREATE TABLE `item_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '????', `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '????', `name` varchar(255) DEFAULT NULL, `category_id` int(11) DEFAULT NULL, `site_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FKgwuxedi20fxclobkk2po053hj` (`category_id`), KEY `FKiujumwssofow95st51ukklpgv` (`site_id`), CONSTRAINT `FKgwuxedi20fxclobkk2po053hj` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`), CONSTRAINT `FKiujumwssofow95st51ukklpgv` FOREIGN KEY (`site_id`) REFERENCES `site` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
我们做个简单的测试, 创建仓库。
package common.repository; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; import common.domain.ItemPool; @Repository public interface ItemPoolRepository extends CrudRepository<ItemPool, Integer> { }
package cn.netkiller.api.restful; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import common.domain.ItemPool; import common.repository.ItemPoolRepository; @RestController public class TestRestController { private static final Logger logger = LoggerFactory.getLogger(TestRestController.class); @Autowired private ItemPoolRepository itemPoolRepository; @GetMapping("/test/json/data/type") public void jsonType() { ItemPool itemPool = new ItemPool(); itemPool.name = "Which is Operstion System?"; Map<String, String> opt = new LinkedHashMap<String, String>(); opt.put("A", "Linux"); opt.put("B", "Java"); itemPool.options = opt; itemPoolRepository.save(itemPool); itemPool = null; itemPool = itemPoolRepository.findOne(1); System.out.println(itemPool.toString()); } }
只能用完美来形容
mysql> select options from item_pool; +-----------------------------+ | options | +-----------------------------+ | {"A": "Linux", "B": "Java"} | +-----------------------------+ 1 row in set (0.00 sec)
package cn.netkiller.domain.demo; import jakarta.persistence.Embeddable; @Embeddable public class Address { private String city; private String district; private String street; private String community; }
package cn.netkiller.domain.demo; import jakarta.persistence.*; @Entity public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; @Embedded private Address address; }
CREATE TABLE `company` ( `id` bigint NOT NULL AUTO_INCREMENT, `city` varchar(255) DEFAULT NULL, `community` varchar(255) DEFAULT NULL, `district` varchar(255) DEFAULT NULL, `street` varchar(255) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
@Data @Embeddable public class Address { private String province; private String city; private String street; }
@Data @Entity @Table public class Company { @Id @GeneratedValue private Long id; @Column private String name; // 公司地址 private Address address; // 注册地址 @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name= "location_city")), @AttributeOverride(name = "province", column=@Column(name="location_province")), @AttributeOverride(name = "street", column = @Column(name="location_street")) }) private Address locationAddress; }
定义实体
package cn.netkiller.wallet.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; @Entity public class UserToken { @EmbeddedId @Column(unique = true, nullable = false, insertable = true, updatable = false) private UserTokenPrimaryKey primaryKey; private String name; private String symbol; private int decimals; public UserToken() { // TODO Auto-generated constructor stub } public UserTokenPrimaryKey getPrimaryKey() { return primaryKey; } public void setPrimaryKey(UserTokenPrimaryKey primaryKey) { this.primaryKey = primaryKey; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSymbol() { return symbol; } public void setSymbol(String symbol) { this.symbol = symbol; } public int getDecimals() { return decimals; } public void setDecimals(int decimals) { this.decimals = decimals; } @Override public String toString() { return "UserToken [primaryKey=" + primaryKey + ", name=" + name + ", symbol=" + symbol + ", decimals=" + decimals + "]"; } @Embeddable public static class UserTokenPrimaryKey implements Serializable { private static final long serialVersionUID = 1242827922377178368L; private String address; private String contractAddress; public UserTokenPrimaryKey() { } public UserTokenPrimaryKey(String address, String contractAddress) { this.address = address; this.contractAddress = contractAddress; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getContractAddress() { return contractAddress; } public void setContractAddress(String contractAddress) { this.contractAddress = contractAddress; } @Override public String toString() { return "UserTokenPrimaryKey [address=" + address + ", contractAddress=" + contractAddress + "]"; } } }
实际效果
CREATE TABLE "user_has_token" ( "address" varchar(255) NOT NULL, "contract_address" varchar(255) NOT NULL, "decimals" int(11) NOT NULL, "name" varchar(255) DEFAULT NULL, "symbol" varchar(255) DEFAULT NULL, PRIMARY KEY ("address","contract_address") ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
package cn.netkiller.wallet.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import cn.netkiller.wallet.domain.UserToken; import cn.netkiller.wallet.domain.UserToken.UserTokenPrimaryKey;; public interface UserTokenRepository extends JpaRepository<UserToken, UserTokenPrimaryKey> { UserToken findOneByPrimaryKey(UserTokenPrimaryKey primaryKey); @Query("select ut from UserToken ut where ut.primaryKey.address=:address") List<UserToken> getByAddress(@Param("address") String address); @Query("select ut from UserToken ut where ut.primaryKey.address=:address and ut.primaryKey.contractAddress=:contractAddress") List<UserToken> findByPrimaryKey(@Param("address") String address, @Param("contractAddress") String contractAddress); }
当尸体返回 Json 数据结构是,将不包含 @JsonIgnore 定义变量。
@JsonIgnore @OneToMany(mappedBy = "owner") private List<Pet> pets;
@SpringBootApplication @EnableJpaAuditing public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application .class, args); } }
在需要审计实体中加入 @EntityListeners(AuditingEntityListener.class)
@EntityListeners(AuditingEntityListener.class) public class Member implements Serializable { private static final long serialVersionUID = -6163675075289529459L; @JsonIgnore String entityName = this.getClass().getSimpleName(); @CreatedBy String createdBy; @LastModifiedBy String modifiedBy; /** * 实体创建时间 */ @Temporal(TemporalType.TIMESTAMP) @CreatedDate protected Date dateCreated = new Date(); /** * 实体修改时间 */ @Temporal(TemporalType.TIMESTAMP) @LastModifiedDate protected Date dateModified = new Date(); #省略getter setter }
@Column(columnDefinition = "int unsigned NOT NULL DEFAULT '0'") @Comment("点赞") private int likes; @Column(columnDefinition = "int unsigned NOT NULL DEFAULT '0'") @Comment("收藏") private int favorites; @Column(columnDefinition = "int unsigned NOT NULL DEFAULT '0'") @Comment("转发") private int forward;
/** * 性别 CHAR(1) 0:女 1:男 */ @Pattern(regexp = "[01]") @Column(name = "gender",columnDefinition = "char(1)") private String gender; /** * 身份证号 CHAR(18) */ @Pattern(regexp = "^([1-6][1-9]|50)\\d{4}(18|19|20)\\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$") @Column(name = "identityCard",columnDefinition = "char(18)") private String identityCard; /** * 所属部门 CHAR(2) 01:金融一部, 02:金融二部, 03:创新中心 */ @Pattern(regexp = "(01|02|03)") @Column(name = "department",columnDefinition = "char(2)") private String department;
B、C 类继承 A 所有属性,并且主键均为数据库(auto_increment)
@MappedSuperclass @(strategy = InheritanceType.TABLE_PER_CLASS) public class A{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; }
@Entity @Table(name="b") public class B extends A{ }
@Entity @Table(name="c") public class C extends A{ }