我的springboot学习【4】

        昨天开了一天会所以没有进度,今天继续。

        首先要试图解决遗留问题。经过了半个多小时终于解决,现在网上找到的资料大多因为版本更迭问题并不实用了,轻则无效重则报错。我来重新说一下这个的具体原因和解决方案:在导入的默认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再具体实现。最后填补了之前挖的统一结果封装的坑,具体实现和延申明天进行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值