| 知乎专栏 |
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'")
@Column(name = "totalAmount", precision = 10, scale = 2, nullable = false) private BigDecimal totalAmount;
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
@Transient 该注解标注的字段不会被应射到数据库当中
settings 不会在数据库中出现,只用于JSON传值
@Transient
private Map<String, Object> settings;
应用举例
package cn.netkiller.domain.device;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
@Entity
@Table(
// uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "business_id"})}
// indexes = {
// @Index(name = "key_device_unique", columnList = "name"),
// @Index(name = "key_device_unique", columnList = "business_id")
//}
)
@DynamicUpdate
@DynamicInsert
@Data
//@Builder
@Comment("设备表")
public class Device implements Serializable {
@Serial
public static final long serialVersionUID = 1L;
@Column(columnDefinition = "int unsigned")
@Comment("存储容量")
private final Integer capacity = null;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "int unsigned")
@Comment("主键")
private Integer id;
@Column(nullable = false, insertable = true, updatable = true, length = 32)
@Comment("设备名称")
private String name;
@Column(unique = true, nullable = false, insertable = true, updatable = false, length = 32)
@Comment("序列号")
private String sn;
@Comment("型号")
@Column(length = 32)
private String model;
@Comment("固件版本")
@Column(length = 32)
private String fireware;
@Comment("软件版本")
@Column(length = 32)
private String version;
// @Column(columnDefinition = "int unsigned")
// @Comment("内存容量")
// private Integer memory;
//
// @Comment("移动网络提供商")
// @Column(columnDefinition = "enum('中国移动','中国电信','中国联通','中国广电','其他') DEFAULT NULL")
// private String provider;
// @Comment("IMEI")
// @Column(length = 15)
// private String imei;
//
// @Comment("IMSI(International Mobile Subscriber Identity)国际移动用户识别码")
// @Column(length = 15)
// private String imsi;
//
// @Comment("MEID")
// @Column(length = 15)
// private String meid;
//
// @Comment("ICCID")
// @Column(length = 20)
// private String iccid;
@Comment("品牌")
private String brand;
@Comment("CPU")
private String cpu;
@Comment("设备驱动")
private String device;
@Comment("显示屏")
private String display;
@Comment("设备标识")
private String fingerprint;
@Comment("硬件")
private String hardware;
@Comment("制造商")
private String manufacturer;
@Comment("产品")
private String product;
@Comment("App 版本")
@Column(length = 8)
private String app;
@Comment("企业微信群")
// @Column(length = 11, insertable = false, updatable = false)
@Column(length = 36)
private String weComGroup;
// @Column(insertable = false, updatable = false, columnDefinition = "int unsigned DEFAULT '0'")
@Comment("微信小程序")
@Column(length = 11)
private String wechat;
@Comment("email")
@Column(length = 64)
private String email;
@Column(length = 32)
@Comment("手机号码")
private String mobile;
@Column(columnDefinition = "DATETIME DEFAULT NULL")
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Comment("到期时间")
private LocalDateTime expired;
@Column(nullable = false, updatable = false, columnDefinition = "enum('Y','N') DEFAULT 'N'")
@Comment("设备状态,Y=正常,N=禁用,冻结,description 会描述冻结原因")
private String status;
@Comment("设备状态原因")
@Column(nullable = false, updatable = false, columnDefinition = "enum('未激活','激活','禁用','冻结','过期','演示') DEFAULT '未激活'")
private String reason;
@Comment("设备状态描述")
private String description;
@Comment("设备状态描述")
private String wechatOpenId;
@Comment("密码")
@Column(length = 40)
private String password;
// @CreatedDate
// @Column(nullable = true, insertable = true, updatable = true)
// @Temporal(TemporalType.TIMESTAMP)
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
// @Comment("访问时间")
// private Date atime;
@Transient
// @JsonProperty
// @JsonInclude(JsonInclude.Include.ALWAYS)
private Map<String, Object> settings;
@Comment("设备归属")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "business_id", updatable = false)
@JsonIgnore
private Business business;
@Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Comment("创建时间")
private LocalDateTime ctime;
@Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Comment("修改时间")
private LocalDateTime mtime;
public Device() {
}
// @OneToOne(mappedBy = "device")
// private Consumer consumer;
// public enum Status{
// Y,N,TRUE,FLASE,ENABLED,DISABLED
// }
// public enum reason{
// "未激活","余额不足","禁用","正常"
// }
}
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;
}
@Temporal 已经被废弃
@Entity
public class Article {
@Id
@GeneratedValue
Integer id;
@Temporal(TemporalType.DATE)
Date publicationDate;
@Temporal(TemporalType.TIME)
Date publicationTime;
@Temporal(TemporalType.TIMESTAMP)
Date creationDateTime;
}
@Temporal(TemporalType.DATE) 替换 private LocalDate birthday;
@Comment("生日")
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthday;
@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)
用 MySQL json 类型保存 List 数据
@Column(columnDefinition = "json")
private List<String> whitelist;
package cn.netkiller.domain.device;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table
@DynamicUpdate
@DynamicInsert
@Data
@Comment("版本表")
public class Version 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 Integer id;
@Comment("商户ID")
private String appId;
@Comment("型号")
@Column(length = 16)
private String model;
@Comment("版本")
@Column(length = 8)
private String version;
@Comment("下载地址")
private String url;
@Comment("编译类型")
@Column(columnDefinition = "enum('release','debug','dev','beta') DEFAULT 'debug'")
private String buildTypes;
@Comment("Checksum 校验")
@Column(length = 32)
private String md5sum;
@Comment("描述")
private String description;
@Comment("状态")
@Column(columnDefinition = "enum('Stable','Unstable','BUG','Gray') DEFAULT 'Stable'")
private String status;
@Comment("灰度白名单,状态为Gray时有效")
@Basic(fetch = FetchType.LAZY)
@Column(columnDefinition = "json")
private List<String> whitelist;
@Column(insertable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@Comment("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDateTime ctime;
@Column(nullable = true, insertable = false, updatable = false, columnDefinition = "TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP")
@Comment("修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime mtime;
}
用法与普通 list 没有任何区别 version.getWhitelist().contains(device)
public Optional<Version> version(String device, String buildTypes) {
Optional<Version> optional = versionRepository.findFirstByBuildTypesOrderByIdDesc(buildTypes);
return optional.map(version -> {
if (version.getStatus().equals("Gray")) {
if (version.getWhitelist() == null) {
return null;
} else if (!version.getWhitelist().contains(device)) {
return null;
}
}
return version;
});
}
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{
}