昨天开了一天会所以没有进度,今天继续。
首先要试图解决遗留问题。经过了半个多小时终于解决,现在网上找到的资料大多因为版本更迭问题并不实用了,轻则无效重则报错。我来重新说一下这个的具体原因和解决方案:在导入的默认jar包,是采用TreeMap的结构存储数据,是一种基于红黑树(二叉树)数据结构实现的,因此会对元素的键自然顺序进行排序处理。这里网络中绝大多数解法是将TreeMap改成HashMap,但这是行不通的,hashmap取元素,都是先拿到元素key值的hashCode,再做hash运算,得到实际存放的下标,再根据下标去取出的,而这个和元素的放入顺序是无关的。很显然这里应该采用LinkedHashMap结构存储。
我们打开依赖的jar包,复制org.hibernate.cfg下的PropertyContainer文件,在java目录下新建一个org.hibernate.cfg文件夹,里面新建PropertyContainer类,将代码粘贴。选中全部TreeMap后改成LinkedHashMap即可解决问题。以下是目前版本的类代码,仅供参考,操作时还是要复制自己版本的代码再修改,这点很重要!
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.hibernate.cfg;
import java.util.*;
import javax.persistence.Access;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.Target;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.cfg.annotations.HCANNHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.jboss.logging.Logger;
class PropertyContainer {
private static final CoreMessageLogger LOG = (CoreMessageLogger)Logger.getMessageLogger(CoreMessageLogger.class, PropertyContainer.class.getName());
private final XClass xClass;
private final XClass entityAtStake;
private final AccessType classLevelAccessType;
private final List<XProperty> persistentAttributes;
PropertyContainer(XClass clazz, XClass entityAtStake, AccessType defaultClassLevelAccessType) {
this.xClass = clazz;
this.entityAtStake = entityAtStake;
if (defaultClassLevelAccessType == AccessType.DEFAULT) {
defaultClassLevelAccessType = AccessType.PROPERTY;
}
AccessType localClassLevelAccessType = this.determineLocalClassDefinedAccessStrategy();
assert localClassLevelAccessType != null;
this.classLevelAccessType = localClassLevelAccessType != AccessType.DEFAULT ? localClassLevelAccessType : defaultClassLevelAccessType;
assert this.classLevelAccessType == AccessType.FIELD || this.classLevelAccessType == AccessType.PROPERTY;
List<XProperty> fields = this.xClass.getDeclaredProperties(AccessType.FIELD.getType());
List<XProperty> getters = this.xClass.getDeclaredProperties(AccessType.PROPERTY.getType());
this.preFilter(fields, getters);
Map<String, XProperty> persistentAttributesFromGetters = new HashMap();
LinkedHashMap<String, XProperty> localAttributeMap = new LinkedHashMap<>();
collectPersistentAttributesUsingLocalAccessType(this.xClass, localAttributeMap, persistentAttributesFromGetters, fields, getters);
collectPersistentAttributesUsingClassLevelAccessType(this.xClass, this.classLevelAccessType, localAttributeMap, persistentAttributesFromGetters, fields, getters);
this.persistentAttributes = verifyAndInitializePersistentAttributes(this.xClass, localAttributeMap);
}
private void preFilter(List<XProperty> fields, List<XProperty> getters) {
Iterator propertyIterator = fields.iterator();
XProperty property;
while(propertyIterator.hasNext()) {
property = (XProperty)propertyIterator.next();
if (mustBeSkipped(property)) {
propertyIterator.remove();
}
}
propertyIterator = getters.iterator();
while(propertyIterator.hasNext()) {
property = (XProperty)propertyIterator.next();
if (mustBeSkipped(property)) {
propertyIterator.remove();
}
}
}
private static void collectPersistentAttributesUsingLocalAccessType(XClass xClass, LinkedHashMap<String, XProperty> persistentAttributeMap, Map<String, XProperty> persistentAttributesFromGetters, List<XProperty> fields, List<XProperty> getters) {
Iterator propertyIterator = fields.iterator();
XProperty xProperty;
Access localAccessAnnotation;
while(propertyIterator.hasNext()) {
xProperty = (XProperty)propertyIterator.next();
localAccessAnnotation = (Access)xProperty.getAnnotation(Access.class);
if (localAccessAnnotation != null && localAccessAnnotation.value() == javax.persistence.AccessType.FIELD) {
propertyIterator.remove();
persistentAttributeMap.put(xProperty.getName(), xProperty);
}
}
propertyIterator = getters.iterator();
while(propertyIterator.hasNext()) {
xProperty = (XProperty)propertyIterator.next();
localAccessAnnotation = (Access)xProperty.getAnnotation(Access.class);
if (localAccessAnnotation != null && localAccessAnnotation.value() == javax.persistence.AccessType.PROPERTY) {
propertyIterator.remove();
String name = xProperty.getName();
XProperty previous = (XProperty)persistentAttributesFromGetters.get(name);
if (previous != null) {
throw new MappingException(LOG.ambiguousPropertyMethods(xClass.getName(), HCANNHelper.annotatedElementSignature(previous), HCANNHelper.annotatedElementSignature(xProperty)), new Origin(SourceType.ANNOTATION, xClass.getName()));
}
persistentAttributeMap.put(name, xProperty);
persistentAttributesFromGetters.put(name, xProperty);
}
}
}
private static void collectPersistentAttributesUsingClassLevelAccessType(XClass xClass, AccessType classLevelAccessType, LinkedHashMap<String, XProperty> persistentAttributeMap, Map<String, XProperty> persistentAttributesFromGetters, List<XProperty> fields, List<XProperty> getters) {
Iterator var6;
XProperty getter;
if (classLevelAccessType == AccessType.FIELD) {
var6 = fields.iterator();
while(var6.hasNext()) {
getter = (XProperty)var6.next();
if (!persistentAttributeMap.containsKey(getter.getName())) {
persistentAttributeMap.put(getter.getName(), getter);
}
}
} else {
var6 = getters.iterator();
while(var6.hasNext()) {
getter = (XProperty)var6.next();
String name = getter.getName();
XProperty previous = (XProperty)persistentAttributesFromGetters.get(name);
if (previous != null) {
throw new MappingException(LOG.ambiguousPropertyMethods(xClass.getName(), HCANNHelper.annotatedElementSignature(previous), HCANNHelper.annotatedElementSignature(getter)), new Origin(SourceType.ANNOTATION, xClass.getName()));
}
if (!persistentAttributeMap.containsKey(name)) {
persistentAttributeMap.put(getter.getName(), getter);
persistentAttributesFromGetters.put(name, getter);
}
}
}
}
public XClass getEntityAtStake() {
return this.entityAtStake;
}
public XClass getDeclaringClass() {
return this.xClass;
}
public AccessType getClassLevelAccessType() {
return this.classLevelAccessType;
}
/** @deprecated */
@Deprecated
public Collection<XProperty> getProperties() {
return Collections.unmodifiableCollection(this.persistentAttributes);
}
public Iterable<XProperty> propertyIterator() {
return this.persistentAttributes;
}
private static List<XProperty> verifyAndInitializePersistentAttributes(XClass xClass, Map<String, XProperty> localAttributeMap) {
ArrayList<XProperty> output = new ArrayList(localAttributeMap.size());
Iterator var3 = localAttributeMap.values().iterator();
while(var3.hasNext()) {
XProperty xProperty = (XProperty)var3.next();
if (!xProperty.isTypeResolved() && !discoverTypeWithoutReflection(xProperty)) {
String msg = "Property " + StringHelper.qualify(xClass.getName(), xProperty.getName()) + " has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type";
throw new AnnotationException(msg);
}
output.add(xProperty);
}
return CollectionHelper.toSmallList(output);
}
private AccessType determineLocalClassDefinedAccessStrategy() {
AccessType hibernateDefinedAccessType = AccessType.DEFAULT;
AccessType jpaDefinedAccessType = AccessType.DEFAULT;
org.hibernate.annotations.AccessType accessType = (org.hibernate.annotations.AccessType)this.xClass.getAnnotation(org.hibernate.annotations.AccessType.class);
if (accessType != null) {
hibernateDefinedAccessType = AccessType.getAccessStrategy(accessType.value());
}
Access access = (Access)this.xClass.getAnnotation(Access.class);
if (access != null) {
jpaDefinedAccessType = AccessType.getAccessStrategy(access.value());
}
if (hibernateDefinedAccessType != AccessType.DEFAULT && jpaDefinedAccessType != AccessType.DEFAULT && hibernateDefinedAccessType != jpaDefinedAccessType) {
throw new org.hibernate.MappingException("@AccessType and @Access specified with contradicting values. Use of @Access only is recommended. ");
} else {
AccessType classDefinedAccessType;
if (hibernateDefinedAccessType != AccessType.DEFAULT) {
classDefinedAccessType = hibernateDefinedAccessType;
} else {
classDefinedAccessType = jpaDefinedAccessType;
}
return classDefinedAccessType;
}
}
private static boolean discoverTypeWithoutReflection(XProperty p) {
if (p.isAnnotationPresent(OneToOne.class) && !((OneToOne)p.getAnnotation(OneToOne.class)).targetEntity().equals(Void.TYPE)) {
return true;
} else if (p.isAnnotationPresent(OneToMany.class) && !((OneToMany)p.getAnnotation(OneToMany.class)).targetEntity().equals(Void.TYPE)) {
return true;
} else if (p.isAnnotationPresent(ManyToOne.class) && !((ManyToOne)p.getAnnotation(ManyToOne.class)).targetEntity().equals(Void.TYPE)) {
return true;
} else if (p.isAnnotationPresent(ManyToMany.class) && !((ManyToMany)p.getAnnotation(ManyToMany.class)).targetEntity().equals(Void.TYPE)) {
return true;
} else if (p.isAnnotationPresent(Any.class)) {
return true;
} else if (p.isAnnotationPresent(ManyToAny.class)) {
if (!p.isCollection() && !p.isArray()) {
throw new AnnotationException("@ManyToAny used on a non collection non array property: " + p.getName());
} else {
return true;
}
} else if (p.isAnnotationPresent(Type.class)) {
return true;
} else {
return p.isAnnotationPresent(Target.class);
}
}
private static boolean mustBeSkipped(XProperty property) {
return property.isAnnotationPresent(Transient.class) || "net.sf.cglib.transform.impl.InterceptFieldCallback".equals(property.getType().getName());
}
}
此时重新运行项目,打开navicat可见,数据库表的字段和实体类属性顺序一一对应。
这里我开始测试各种方法,遇到了一大堆问题。到目前为止历时两小时仍未解决。我本人是想通过save方法存储数据,之后再验证其他方法,但是这里似乎系统自动生成的sql语句有问题,values的值全都是“?”,并且我始终找不到解决的办法。因此我先去测试其他方法。在navicat里输入了两组数据,这里尝试拿到全部数据,成功。在尝试按照id来查找数据时报了500错误。经过很多次验证后,确定是getById方法已经无法使用,官方给出的方法是findById。但是这里会爆红,原因是高版本里应该这样写:findById(id).get()。代码如下:
@Override
public User getUserById(Integer id) {
return userDao.findById(id).get();
}
改完函数后我们重新运行项目,得到了想要的结果,按id查找成功。
奥对了,补充说明一个贼烦人的东西。我之前并没有设计打开ApiPost就启动浏览器插件。跟我设置相同的情况下要记得运行插件,否则这进度条转无数圈也白扯。
目前我仍未解决save方法的错误,这应该是JpaRepository接口提供的方法出了错误。JPA没有直接给出update函数,我这里采用的是set配合save进行,由于save并没得到解决所以先去看一下删除功能。函数如下:
@GetMapping("/delete/{id}")
public String deleteById(@PathVariable Integer id){
userService.delete(id);
return "已删除,请查看数据库";
}
有了按照id查看的先例,这个函数写起来简单得多无需多言。接下来我又要花时间去试图解决save的问题了。奇怪的是,我换了用form的形式输入数据,居然就保存成功了。。。json形式就不行。。。我也不知道这是为啥,反正他突然就好使了。于是,update操作也成功了。。。
我也不知道原因是什么,先把整个Controller层代码合起来粘一下吧。
package com.lxc.Controller;
import com.lxc.Service.UserService;
import com.lxc.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/jpa")
public class JpaController {
@Autowired
private UserService userService;
public JpaController(UserService userService) {
this.userService = userService;
}
@GetMapping("/")
public String test1(){
return "这里一切正常,请查看下一层";
}
@GetMapping("/all")
public List<User> userList(){
return userService.listUser();
}
@GetMapping("/delete/{id}")
public String deleteById(@PathVariable Integer id){
userService.delete(id);
return "已删除,请查看数据库";
}
@PostMapping("/save")
public String save(User user){
userService.save(user);
return "已存入数据库,请查看";
}
@PostMapping("/update")
public String update(User user){
userService.update(user.getId(), user);
return "数据已更新,请查看";
}
@GetMapping("/get/{id}")
public User getById(@PathVariable Integer id){
User user = userService.getUserById(id);
return user;
}
}
加入@RequestBody注解后,json格式的参数就可以传到后端。目前传参最常用的方式是json,至于传文件,我问了老师,现在MultipartFile是应用最广的,择日不如撞日我打算现在就来玩一下这个工具。当然了先把save函数和update函数改一下。这里只写save,update同理。
@PostMapping(value = "/save")
public String save(@RequestBody User user){
//System.out.println("user: "+user);
userService.save(user);
return "已存入数据库,请查看";
}
到此为止,数据库的增删改查功能已经能够基本实现,先去学点其他的东西,等要换换脑子的时候再来看自定义的问题。
接下来看一下MultipartFile工具。MultipartFile是一种可以接收使用多种请求方式来进行上传文件的代表形式,可以通俗的理解为以表单的形式提交,可以存储到内存中或者存储在磁盘的临时位置上。这种临时存储会在请求结束后被消除掉。MultipartFile是一个继承了InputStreamSource的接口,并且封装了getInputStream方法,返回类型为InputStream类型。
这里列举八个常用方法吧。①.getName()方法,获取的是前后端约定的传入文件的参数的名称,后台中则是通过@Param("uploadFile") 注解定义的内容,没有定义@Param("uploadFile"),则接收不到文件②.getOriginalFileName()方法,获取文件完整名称,包括后缀③.getContentType方法,获取文件类型④.isEmpty()方法,判断文件是否为空⑤.getBytes()方法,将文件转化为字节数组传输,会抛出IOException异常⑥.getSize()方法,获取文件大小⑦.getInputStream()方法,将文件转化为输入流传输⑧.transferTo()方法,将文件传输给目标路径,会抛出IOException、IllegalStateException异常,这个方法用的较少。
在使用MultipartFile时,可以声明为一个数组来进行多文件传输。可以根据MultipartFile的.getSize()方法来控制传输来的文件大小。理论部分到此结束,实战部分将在后面学习swagger时候进行。
下面我打算捡一下之前落下的知识点——结果统一封装和统一异常问题。先看统一封装,有几天没看了这东西我再总结一下,首先要有一个code代表我接口的状态,其次要有一个重要消息的提示字段message,最后要有一个返给前端的数据泛型data。
首先来做全部的code码信息,这里是我们自定义的,因此可以用一个枚举来进行全部设置。
package com.lxc.resultMSG;
public enum ResultCodeEnum {
SUCCESS(0, "操作成功"),
ERROR(1, "操作失败"),
BIZ_ERROR(1000, "通用业务异常"),
FILE_OUT_MAX(9000, "文件超出最大限制"),
FILE_FORMAT_ERROR(9001, "文件格式不正确"),
PARAM_ERROR(9050, "参数错误"),
JSON_FORMAT_ERROR(9051, "Json解析异常"),
SQL_ERROR(9052, "Sql解析异常"),
NETWORK_TIMEOUT(9510, "网络超时"),
UNKNOWN_INTERFACE(9520, "未知的接口"),
REQ_MODE_NOT_SUPPORTED(9530, "请求方式不支持"),
SYS_ERROR(9999, "系统异常");
private final int code;
private final String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
ResultCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
有了接口状态码枚举之后,就可以轻而易举定义错误类型了。
package com.lxc.resultMSG;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Data
@Builder
public class ResultCode implements Serializable {
private static final long serialVersionUID = -6269841958947880397L;
private int code;
private String msg;
//默认成功
public final static ResultCode SUCCESS = dispose(ResultCodeEnum.SUCCESS);
//默认失败
public final static ResultCode ERROR = dispose(ResultCodeEnum.ERROR);
//通用业务异常
public final static ResultCode BIZ_ERROR = dispose(ResultCodeEnum.BIZ_ERROR);
//文件超出最大限制
public final static ResultCode FILE_OUT_MAX = dispose(ResultCodeEnum.FILE_OUT_MAX);
//文件格式不正确
public final static ResultCode FILE_FORMAT_ERROR = dispose(ResultCodeEnum.FILE_FORMAT_ERROR);
//参数错误
public final static ResultCode PARAM_ERROR = dispose(ResultCodeEnum.PARAM_ERROR);
//Json解析异常
public final static ResultCode JSON_FORMAT_ERROR = dispose(ResultCodeEnum.JSON_FORMAT_ERROR);
//Sql解析异常
public final static ResultCode SQL_ERROR = dispose(ResultCodeEnum.SQL_ERROR);
//网络超时
public final static ResultCode NETWORK_TIMEOUT = dispose(ResultCodeEnum.NETWORK_TIMEOUT);
//未知接口
public final static ResultCode UNKNOWN_INTERFACE = dispose(ResultCodeEnum.UNKNOWN_INTERFACE);
//请求方式不支持
public final static ResultCode REQ_MODE_NOT_SUPPORTED = dispose(ResultCodeEnum.REQ_MODE_NOT_SUPPORTED);
//系统异常
public final static ResultCode SYS_ERROR = dispose(ResultCodeEnum.SYS_ERROR);
private static ResultCode dispose(ResultCodeEnum codeEnum) {
return ResultCode.builder().code(codeEnum.getCode()).msg(codeEnum.getMsg()).build();
}
public ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
最后就可以进行泛型类的定义了,这个类需要包含的信息前面已经分析过,直接上代码吧。
package com.lxc.resultMSG;
import java.io.Serializable;
public class Result<T> implements Serializable {
private static final long serialVersionUID = -3960261604605958516L;
private int code;
private String msg;
private T data;
//lombok省略get、set方法
public static <T> Result<T> success(){
return new Result<>();
}
public static <T> Result<T> success(T data){
return new Result<>(data);
}
public static <T> Result<T> success(String msg) {
return new Result<>(msg);
}
public static <T> Result<T> error() {
return new Result<>(ResultCodeEnum.ERROR);
}
public static <T> Result<T> error(String msg) {
return new Result<>(ResultCodeEnum.ERROR.getCode(), msg);
}
//失败,自定义状态码,返回消息,无返回数据
public static <T> Result<T> error(int code, String msg) {
return new Result<>(code, msg);
}
//失败,使用CodeMsg状态码,返回消息,无返回数据
public static <T> Result<T> error(ResultCodeEnum resultCode) {
return new Result<>(resultCode);
}
//成功构造器,无返回数据
private Result() {
this(ResultCodeEnum.SUCCESS);
}
//成功构造器,自定义返回数据
private Result(T data) {
this(ResultCodeEnum.SUCCESS, data);
}
//成功构造器,自定义返回消息,无返回数据
private Result(String msg) {
this(ResultCodeEnum.SUCCESS.getCode(), msg);
}
//成功构造器,自定义返回信息,返回数据
private Result(String msg, T data) {
this(ResultCodeEnum.SUCCESS.getCode(), msg, data);
}
//构造器,自定义状态码,返回消息
private Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
//构造器,自定义状态码,返回消息,返回数据
private Result(int code, String msg, T data) {
this(code, msg);
this.data = data;
}
//构造器,使用CodeMsg状态码与返回信息
private Result(ResultCodeEnum resultCode) {
this(resultCode.getCode(), resultCode.getMsg());
}
//构造器,使用CodeMsg状态码与返回信息,自定义返回数据
private Result(ResultCodeEnum resultCode, T data) {
this(resultCode);
this.data = data;
}
}
到此为止,统一结果的封装定义已经完成,具体实现的工作就放到明天吧,不止于查看是否正常工作,还要讲前面的JPA部分函数返回值类型定义成这个统一结果类。
今天剩下一些时间,写一下今天的心得。今天做的事情并不多,尤其是还有绝大多数时间用来处理上周遗留的问题。首先是JPA的部分,应用系统给的JpaRepository接口可以解决简单的数据库问题,它仍然是依赖于hibernate的,并且能够自动生成数据库语句。其最大的特点是能够根据实体类直接映射到数据库,大大降低了程序员的工作量。我花了大量时间解决实体类属性和数据库表字段不对应的问题,以及@PostMapping注解后json和form的返回值问题。其次是Multipart部分,了解了一些基本概念,以后学习swagger再具体实现。最后填补了之前挖的统一结果封装的坑,具体实现和延申明天进行。