记录一些问题

1、如何下载从数据库中查询出来的数据

查询结果List 写到文件中,然后下载

 @GetMapping(value = "/download")
    public void download(HttpServletResponse response)
            throws IOException {
        List<ticket> tickets = getTickets();
        File tmpFile = write2CSVFile(tickets);
             
        final String fileName = tmpFile.getName();
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader("Content-Disposition",
                           "attachment;filename=" + fileName);
        FileUtils.readToOutputStream(tmpFile.getPath(),
                                     response.getOutputStream(), 0);
        response.getOutputStream().close();
    }

FileUtils

 /**
     * @param filePath    path of the file which work as the input source
     * @param outputStream the OutputStream to which the data is written
     * @param skippedSize  the size of the bytes which is skipped for every file
     */
    public static OutputStream readToOutputStream(
            String filePath, OutputStream outputStream,
            long skippedSize) throws IOException {
        int bytesRead;
        BufferedOutputStream bufferedOutputStream =
                new BufferedOutputStream(outputStream);
        byte[] buffer = new byte[1024 * 1024];

        try (BufferedInputStream inputStream =
                     new BufferedInputStream(
                             new FileInputStream(filePath))) {
            inputStream.skip(skippedSize);
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                bufferedOutputStream.write(buffer, 0, bytesRead);
            }
        }

        bufferedOutputStream.flush();
        return bufferedOutputStream;
    }

2、mybatis plus in 参数个数超过限制报错

mybatis plus in 参数个数限制1000条。
解决办法:
in 大集合 改为 多个in 小集合
即分多个in 用or连接

notIn同理,
notIn分为多个 notIn 用and连接

   // run sql
            LambdaQueryWrapper<Ticket> lambdaQueryWrapper =
                    new LambdaQueryWrapper<>();
            // mybatis plus in 参数个数超过限制报错
            try {
                cutInParameter(lambdaQueryWrapper, Ticket::getOneId,
                               oneIdListWithWhitelist);
            } catch (Exception e) {
                log.error("cut parameter fail", e);
                throw new BusinessException("cut parameter fail");
            }
  public static <T, F> void cutInParameter(LambdaQueryWrapper<T> wrapper,
                                             SFunction<T, ?> column,
                                             List<F> coll) throws Exception {
        List<List<F>> newList = splitList(coll, 900);
        if (ObjectUtils.isEmpty(newList)) {
            return;
        } else if (newList.size() == 1) {
            wrapper.notIn(column, newList.get(0));
            return;
        }
        wrapper.and(i -> {
            i.notIn(column, newList.get(0));
            newList.remove(0);
            for (List<F> objects : newList) {
                i.and(w -> {
                    w.notIn(column, objects);
                });
            }
        });
    }

    public static <T> List<List<T>> splitList(List<T> list, int groupSize) {
        int length = list.size();
        // 计算可以分成多少组
        int num = (length + groupSize - 1) / groupSize;
        List<List<T>> splitList = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            // 开始位置
            int fromIndex = i * groupSize;
            // 结束位置
            int toIndex = Math.min((i + 1) * groupSize, length);
            splitList.add(list.subList(fromIndex, toIndex));
        }
        return splitList;
    }

3、springboot 自定义类加载器

利用springboot的maven插件打包jar,在java -jar运行时,会采用springboot自定义的类加载器来加载类,使用的是LaunchedURLClassLoader,(使用assembly插件不会有这个问题),
但是代码在IDEA中能够运行,但是打包成jar之后无法运行,报ClassCastException

 			<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
<plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <id>agent</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <attach>false</attach>
                            <outputDirectory>${project.basedir}</outputDirectory>
                            <finalName>${final.name}</finalName>
                            <appendAssemblyId>false</appendAssemblyId>
                            <descriptors>
                                <descriptor>
                                    ${assembly.descriptor.dir}/assembly.xml
                                </descriptor>
                            </descriptors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

类的唯一性如何确定:
由加载这个类的类加载器和类本身来唯一确定一个类,也就是说类相同,但是类加载器不同也会被认为是不同的类。

  • 类加载器相同,指的是同一个类加载器对象(不同实例也是不同的类加载器),而不是类加载器的类相同

在业务代码中我们可能需要加载外部指定路径的jar,加载jar时我们一版使用java.net.URLClassLoader,此时如果存在外部jar和springboot加载的类之间有关联,比如有一个类两个类加载器都加载了,那么会被认为是不同的类,由此可能引起ClassCastException

在IDEA中能运行的原因是:在IDEA中统一使用了AppClassLoader这个加载器,这个加载器也称为系统类加载器。
类加载器双亲委派机制
每个加载器加载的位置不同。

一般会使用的方式,url类似 file:/path/to/jar


 private URLClassLoader loader;

 private Properties props = new Properties();
    
  public void loadJar(URL url) throws IOException {

        loader = new URLClassLoader(new URL[]{url});

        try (InputStream stream = loader
                .getResourceAsStream(AdaptorPropsList.HIGHFLIP_PROPERTIES_FILE)) {

            if (stream != null) {
                props.load(stream);
            } else {
                log.info("Missed {} property file.", AdaptorPropsList.HIGHFLIP_PROPERTIES_FILE);
            }
        }
    }

springboot 使用LaunchedURLClassLoader来加载类,此时加载外部jar包用java.net.URLClassLoader,这样类加载器不一致,会引发问题,正确的加载类的方式:

    private URLClassLoader loader;

    private Properties props = new Properties();

    public void loadJar(URL url) throws IOException {

        if (Thread.currentThread()
                  .getContextClassLoader() instanceof LaunchedURLClassLoader) {
            loader = (LaunchedURLClassLoader) Thread.currentThread()
                                                    .getContextClassLoader();
            try {
                Method addURL =
                        LaunchedURLClassLoader.class.getSuperclass()
                                                    .getDeclaredMethod("addURL",
                                                                       new Class[] {URL.class});
                addURL.setAccessible(true);
                addURL.invoke(loader, new Object[] {url});
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            loader = new URLClassLoader(new URL[] {url});
        }

        try (InputStream stream = loader
                .getResourceAsStream("app.properties")) {

            if (stream != null) {
                props.load(stream);
            } else {
                log.info("Missed {} property file.", "app.properties");
            }
        }
    }

获取当前线程的类加载器

Thread.currentThread().getContextClassLoader()

在这里插入图片描述

LaunchedURLClassLoader类所在的依赖包

 <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-loader</artifactId>
</dependency>

LaunchedURLClassLoader部分源码

public class LaunchedURLClassLoader extends URLClassLoader {
   private static final int BUFFER_SIZE = 4096;
   private final boolean exploded;
   private final Archive rootArchive;
   private final Object packageLock;
   private volatile LaunchedURLClassLoader.DefinePackageCallType definePackageCallType;

   public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
       this(false, urls, parent);
   }

   public LaunchedURLClassLoader(boolean exploded, URL[] urls, ClassLoader parent) {
       this(exploded, (Archive)null, urls, parent);
   }

   public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
       super(urls, parent);
       this.packageLock = new Object();
       this.exploded = exploded;
       this.rootArchive = rootArchive;
   }
   
...
}

源码中可以看到

  • LaunchedURLClassLoader没有提供addURL的方法来加载指定路径的jar包
  • LaunchedURLClassLoader继承了URLClassLoader
  • URLClassLoader提供了addURL的方法,但是是protected方法,调用不到

为了能调用addURL方法,采用反射机制来调用,先反射拿到LaunchedURLClassLoader.class类,再getSuperclass()拿到URLClassLoader的类对象,添加url,完成加载外部指定路径的jar包。

4、mybatis plus打印sql语句

application.yaml中添加配置:

mybatis-plus:
  configuration:
     ### 开启打印sql配置
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    ### 开启驼峰配置
    map-underscore-to-camel-case: true

5、URL 编码

+为URL编码保留字符,不会被编码,
空格URL编码后为+
http://server.com/a/path?key1=value1&key2=value2
如果value1中的参数有+,这时候 后端收到的会decode将+号解码为空格,
比如value1=aaa+bbb,后端key1得到的值是aaa bbb

后端如何解决这种问题?

value1.replaceAll(" ", "+")

6、获取当前jar的目录

获取当前jar在机器上的目录:

    /**
     * 获取jar包同目录
     *
     * @return
     */
    private String getJarFilePath() {
        ApplicationHome home = new ApplicationHome(getClass());
        File jarFile = home.getSource();
        return jarFile.getParentFile().toString();
    }

注意此方法使用时jar包必须存在,否则会报错。

7 Jackson: java.util.LinkedHashMap cannot be cast to X

Jackson反序列化时转换异常。objectMapper.readValue(),当没有指定类型时,会将object类型反序列化为LinkedHashMap类型
Jackson:
when Jackson attempts to deserialize an object in JSON but no target type information is given, it’ll use the default type: LinkedHashMap.

如何解决这种问题:
反序列化时,传入类型
** we can pass a TypeReference object to the objectMapper.readValue(String content, TypeReference valueTypeRef) method.**

eg:

List<Book> bookList = objectMapper.readValue(jsonString, new TypeReference<List<Book>>() {});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值