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
类的唯一性如何确定:
由加载这个类的类加载器和类本身来唯一确定一个类,也就是说类相同,但是类加载器不同也会被认为是不同的类。
- 类加载器相同,指的是同一个类加载器对象(不同实例也是不同的类加载器),而不是类加载器的类相同
在业务代码中我们可能需要加载外部指定路径的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,这样类加载器不一致,会引发问题,正确的加载类的方式:
public void loadJar(URL url) throws IOException {
private 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);
}
}
获取当前线程的类加载器
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包。