目录
entity实体类相关知识点详解:
解释1:上面代码使用的注解是 Lombok 提供的注解,用于简化实体类的开发。
解释2:属性的注释自动生成问题:
解释3:java序列化反序列化,实体类实现Serializable接口:
java序列化和反序列化
实现序列化和反序列化实现Serializable接口原因及注意事项:
解答:现在前后端数据传输和存储都使用JSON这种数据格式了,让公共返回对象实现Serializable接口,实现序列化成字节序列。这是使用@responseBody,又转成json返回形式进行网络传输,JSon序列后的字符串会自动转成字节流吗?是怎么样的流程?
例子:
/**
* 秒杀订单 表(t_seckill_order)实体类
*
* @author Z
* @since 2023-09-30
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_seckill_order")
public class SeckillOrder implements Serializable {
//序列化
private static final long serialVersionUID = 1L;
/**
* 秒杀订单ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 订单ID
*/
private Long orderId;
/**
* 商品ID
*/
private Long goodsId;
//下订单时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private String time;
//数据库中没有的字段
@TableField(exist = false)
private String xxxxxx;
}
@Data : 它会自动生成类的所有属性的 getter、setter 方法、equals() 和 hashCode() 方法以及 toString()方法。
@AllArgsConstructor :为类生成一个无参构造函数 @NoArgsConstructor:为类生成一个带参数的构造函数
@EqualsAndHashCode :它生成 equals()和 hashCode()方法。通过 callSuper = false参数,表示不调用父类的 equals() 和 hashCode() 方法,(默认是为ture)。这通常用于不需要考虑继承关系的情况。
@TableName 是 MyBatis-Plus 提供的注解:用于指定实体类对应的数据库表名。在这个例子中,@TableName("t_order") 表示该实体类映射到数据库中的 "t_order" 表。
@TableId是 MyBatis-Plus 提供的注解:用于标识实体类的主键字段,并指定主键的生成策略。在这个例子中,@TableId(value = "id", type = IdType.AUTO) 表示 "id" 字段是主键,并且采用自动增长的方式生成主键值。
@TableField(exist = false) 是 MyBatis-Plus 提供的注解:用于指定实体类中某个属性在数据库表中不存在。该注解表示与数据库中的字段没有映射,在下面的情况很常用:
计算字段:某个属性是通过其他字段计算得出的,不需要在数据库中保存。
关联字段:某个属性是与其他表进行关联(外键),而不是直接存储在当前表中
@Accessors(chain = true) : 用于链式调用风格的编程.例如:
在使用 @Accessors(chain = true) 注解后,会自动生成下面的 setter 方法:
javaCopy Codepublic User setId(Long id) {
this.id = id;
return this;
}
public User setUsername(String username) {
this.username = username;
return this;
}
public User setPassword(String password) {
this.password = password;
return this;
}
通过链式调用的方式,可以简化对象属性的设置过程:
User user = new User()
.setId(1L)
.setUsername("john")
.setPassword("password");
@JSONField(format="yyyy-MM-dd HH:mm:ss") 是阿里巴巴的 fastjson 库提供的注解,用于指定日期时间类型属性在序列化为 JSON 字符串时的格式。
实体类的代码是可以通过逆向工程(MyBatis-Plus代码生成器)自动生成的,并可以自动提取数据库表的字段注释作为实体类属性的注释。
序列化:将对象转换成字节序列的过程,可以将对象持久化到本地磁盘或通过网络传输到远程机器。
反序列化:将字节序列转换回对象的过程
在网络传输中,数据需要以字节流的形式进行传输。字节流是指以字节为单位进行读取和写入的数据流。
而字节序列是指一系列字节的排列顺序。在对象序列化过程中,将对象转换为一连串的字节序列,这些字节按照特定的顺序组织起来,表示对象的各个属性和状态。因此,字节序列可以看作是对象序列化后得到的字节流。
序列化和反序列化的应用场景(本地JVM中运行java实例不需要序列化):
网络通信:在网络通信中,可以将对象进行序列化后通过网络传输到远端机器,这样可以实现远程过程调用(RPC)、分布式系统的协作等功能。如前端和后端之间需要进行交互和数据传输,这就需要通过网络实现通信。
数据持久化:通过序列化,可以将对象保存到磁盘或者数据库中,以便之后读取和恢复对象的状态。这在需要长期存储和恢复对象的情况下非常有用,例如将对象保存到文件系统、缓存、关系型数据库或NoSQL数据库中。
缓存机制:序列化和反序列化也广泛应用于缓存中,对于频繁访问的数据,可以将其序列化后存储到缓存中,减少数据库的访问次数,提高系统性能。
分布式计算:在分布式计算中,可以通过序列化和反序列化实现数据的传递和共享。例如,通过序列化和反序列化可以将任务对象发送给不同的计算节点,并在节点上执行任务。
消息队列:消息队列中的消息通常需要进行序列化和反序列化处理。生产者将消息对象序列化后发送到消息队列,消费者从队列中接收消息并进行反序列化,以获取消息内容。如RabbitMQ 进行消息传递时,序列化和反序列化是非常重要的步骤。
进程间通信:在进程间通信(IPC)中,通过序列化和反序列化可以在不同的进程之间传递数据。例如,通过管道、套接字等方式进行进程间通信时,可以将对象序列化后发送给接收方进程进行反序列化处理。
如果要将一个 Java 对象序列化为字节流,那么该对象必须实现Serializable接口。该接口中没有任何方法,只是作为一个标记接口,告诉 JVM,该类可以被序列化,并且需要把哪些属性序列化到字节流中。
而实现Serializable接口还需要注意下面2个点:
1.序列化版本号:即指定serialVersionUID的值。如果不显示指定serialVersionUID, JVM在序列化时会根据类的细节自动生成一个serialVersionUID值, 然后与属性一起序列化。(具体来说,JVM 会基于类的名称、访问修饰符、非瞬态和非静态字段以及方法和构造函数等细节计算出一个长整型数值,并将其作为默认的 serialVersionUID 值。) 再进行持久化或网络传输. 在反序列化时, JVM会再根据细节自动生成一个新版serialVersionUID, 然后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID进行比较, 相同则反序列化成功, 否则报InvalidClassException 异常。
但是这种自动生成的serialVersionUID 可能因为修改类定义而发生改变,从而导致无法正确地进行对象的反序列化。因此,虽然 serialVersionUID 的确可以被自动生成,但是我们无法避免版本不会迭代,类不再修改。因此我们可以指定一个serialVersionUID,防止这种情况的发送。
显式地定义serialVersionUID 常量,可以是任意的 long 常量,但建议使用固定的值,通常是 1L。
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order")
public class Order implements Serializable {
//设置了serialVersionUID
private static final long serialVersionUID = 1L;
/**
* 订单ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 商品ID
*/
private Long goodsId;
/**
* 收货地址ID
*/
private Long deliveryAddrId;
/**
* 支付时间
*/
private Date payDate;
}
2.transient 关键字:某些属性不应该被序列化到字节流中,可以使用 transient 关键字来标记这些属性。
假设我们有一个需要进行序列化的 Java 类,而因为密码很敏感,不希望在网络传输或存储时暴露出来。所以password 属性被标记为 transient 关键字,意味着该属性不会被序列化到字节流中。
public class User implements Serializable {
private String username;
private transient String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// 省略 getter 和 setter 方法
}
创建一个 User 对象进行序列化和反序列化操作:
User user = new User("Alice", "123456");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.obj"));
out.writeObject(user);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.obj"));
User newUser = (User) in.readObject();
in.close();
在序列化时,JVM 会自动将 username 和 password 属性编码为二进制数据流,并写入到字节流中。但是,在反序列化时,JVM 只能恢复 username 属性,而 password 属性则被忽略了。此时,新建的 User 对象的 password 属性值为 null。
步骤:先使用Jackson 的 ObjectMapper
将 Java 对象转换为 JSON 数据时,ObjectMapper
会将 JSON 数据序列化为字节序列。当从网络中接收到字节序列后,ObjectMapper
可以将其反序列化为 JSON 数据,再转换为对应的 Java 对象。
JSON序列化:将Java对象转换为JSON字符串的过程。
JSON反序列化是将JSON字符串转换为Java对象的过程。
JSON 是存储和交换文本信息的语法。类似 XML。Spring Boot 中,通常使用 Jackson 库来处理 JSON 数据的序列化和反序列化。在 TCP/IP 网络传输中;JSON 字符串会被转换为字节流进行传输。传输过程中,JSON 字符串会被转换为字节流进行传输:
首先,将 JSON 字符串按照某种编码方式(如 UTF-8)进行编码,将字符串中的每个字符转换为对应的字节序列;
然后,将编码后的字节序列按照客户端和服务器之间的约定进行传输;
最后,在接收端,根据约定的编码方式将接收到的字节序列解码为 JSON 字符串。
例如:序列化
ObjectMapper mapper = new ObjectMapper();
User user = new User();
String json = mapper.writeValueAsString(user);
byte[] bytes = json.getBytes("UTF-8");
反序列化
byte[] bytes = receiveJsonFromNetwork();
String json = new String(bytes, "UTF-8");
ObjectMapper mapper = new ObjectMapper();
USer user = mapper.readValue(json, User.class);
我们也可以使用JSON工具类
@Component
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
/**
* Object转json字符串
*
* @param obj
* @param
* @return
*/
public static String object2JSonStr(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
System.out.println("Parse object to String error");
e.printStackTrace();
return null;
}
}
/**
* Object转json字符串并格式化美化
*
* @param obj
* @param
* @return
*/
public static String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
System.out.println("Parse object to String error");
e.printStackTrace();
return null;
}
}
/**
* string转object
*
* @param str json字符串
* @param clazz 被转对象class
* @param
* @return
*/
public static T JsonStr2Object(String str, Class clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz);
} catch (IOException e) {
System.out.println("Parse String to Object error");
e.printStackTrace();
return null;
}
}
/**
* string转object 用于转为集合对象
*
* @param str json字符串
* @param collectionClass 被转集合class
* @param elementClasses 被转集合中对象类型class
* @param
* @return
*/
public static T JsonStr2Object(String str, Class> collectionClass, Class>... elementClasses) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return objectMapper.readValue(str, javaType);
} catch (IOException e) {
System.out.println("Parse String to Object error");
e.printStackTrace();
return null;
}
}
}