java的TimeUtils或者DateUtils的编写心得
一、几种常见的日期和时间类介绍
介绍时间工具类不可避免必须要去触碰几个常见的日期和时间类,所以就简单介绍一下。
1、jdk1.8之前的日期时间类
a、Date类
我们可以通过new的方式生成一个Date对象,构造函数有参的和无参的,无参的是获取当前的系统的时间,Date这个类有不少过期的方法,而且Date是线程不安全的,所以当你需要考虑线程安全的情况时,Date其实使用起来有一定的局限。Date类中有fastTime成员变量,所以对于一个Date来说,存有时间戳的,这就给各种日期和时间对象的的转换提供了可能。
b、Calendar类
这是一个抽象类,其有多个子类补充了很多其他的功能。Calendar翻译过来就是日历,所以我们可以获取日期哪一年、哪一个月和哪一天,以及计算和获取某个月的第一天等等。Calendar也是线程不安全的,其中的set方法其实可以修改Calendar中的成员变量。
c、SimpleDateFormat
在java8之前我们一般习惯使用SimpleDateFormat这个类来进行时间转换成字符串的操作。但是这个类是线程不安全的,这就意味着我们在转换时候存在着转换风险,当然我们有解决的方法,一个是使用本地变量ThreadLocal存放SimpleDateFormat,如果使用Map来存放,其实在生成的时候还是有线程安全的问题,这里我们使用悲观锁来做一下限制,当然也可以使用线程安全的Map(Hashtable、synchronizedMap、ConcurrentHashMap)。
下面使用synchronized加锁:
/** 锁对象 */
private static final Object lockObj = new Object(); /** 存放不同的日期模板格式的sdf的Map */
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>(); /**
* 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
*
* @param pattern
* @return
*/
private static SimpleDateFormat getSdf(final String pattern) {
ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern); // 生成的时候我们需要去考虑线程问题,Map并没有做线程处理,我们可以
if (tl == null) {
synchronized (lockObj) {
tl = sdfMap.get(pattern);
if (tl == null) {
// 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
System.out.println("put new sdf of pattern " + pattern + " to map"); // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
tl = new ThreadLocal<SimpleDateFormat>() { @Override
protected SimpleDateFormat initialValue() {
System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
return new SimpleDateFormat(pattern);
}
};
sdfMap.put(pattern, tl);
}
}
} return tl.get();
} /**
* ThreadLocal的原理是,获取一个静态变量的副本,这个其实是牺牲空间换取时间的案例
*
* @param date
* @param pattern
* @return
*/
public static String format(Date date, String pattern) {
return getSdf(pattern).format(date);
} public static Date parse(String dateStr, String pattern) throws ParseException {
return getSdf(pattern).parse(dateStr);
}
当然我们也可以使用线程安全的map:
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new Hashtable<String, ThreadLocal<SimpleDateFormat>>();
d、sql包中其实也有几个时间的类 java.sql.Date/Time/Timestamp
首先这几个类继承自util包中的Date类,相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。平时用的不是很多。也是线程不安全的类。
2、java8以后的时间日期类
在java8以后新增加了date-time包
a、Instant
这个类在java8之前和之后的时间日期类中都提供了转换的方法,这样就能很明确的通过Instant这个中间变量实现,java8之前和之后的时间日期类的相互转换。但是我们需要注意的是,Instant主要维护的是秒和纳秒字段,可以表示纳秒范围,如果不符合转换条件,就会抛出异常。
以Date类为例,看一下源码:
public static Date from(Instant instant) {
try {
return new Date(instant.toEpochMilli());
} catch (ArithmeticException ex) {
throw new IllegalArgumentException(ex);
}
} /**
* Converts this {@code Date} object to an {@code Instant}.
* <p>
* The conversion creates an {@code Instant} that represents the same
* point on the time-line as this {@code Date}.
*
* @return an instant representing the same point on the time-line as
* this {@code Date} object
* @since 1.8
*/
public Instant toInstant() {
return Instant.ofEpochMilli(getTime());
}
b、Clock
有获取当前时间的方法,也可以获取当前Instant,Clock是有时区或者说时区偏移量的。Clock是一个抽象类,其内部有几个子类继承自Clock。
几个抽象方法:
public abstract ZoneId getZone();
public abstract Clock withZone(ZoneId zone);
public long millis() {
return instant().toEpochMilli();
}
public abstract Instant instant();
c、ZoneId/ZoneOffset/ZoneRules
ZoneId和ZoneOffset都是用来代表时区的偏移量的,一般ZoneOffset表示固定偏移量,ZoneOffset
表示与UTC时区偏移的固定区域(即UTC时间为标准),不跟踪由夏令时导致的区域偏移的更改;ZoneId
表示可变区偏移,表示区域偏移及其用于更改区域偏移的规则夏令时。这里举一个简单的例子,美国东部时间,我们可以使用zoneId来表示,应为美国使用的是冬令时和夏令时的时候时间是有区别的,和中国的时差会有一个小时的差别。而ZoneRules
跟踪区域偏移如何变化,时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。
常用的几个:
//美东时间
public static final String TIMEZONE_EST_NAME = "US/Eastern";
public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
//北京时间
public static final String TIMEZONE_GMT8_NAME = "GMT+8";
public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME);
public static final ZoneOffset BEIJING_ZONE_OFFSET =ZoneOffset.of("+08:00");
public static final ZoneOffset STATISTIC_ZONE_OFFSET =ZoneOffset.of("+03:00");
private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York");
private static final ZoneId SHANGHAI_ZONE_ID = ZoneId.of("Asia/Shanghai");
d、LocalDateTime/LocalTime/LocalDate/ZoneDateTime
LocalDateTime/LocalTime/LocalDate都没有时区的概念,其中LocalDate主要是对日期的操作,LocalTime主要是对时间的操作,LocalDateTime则是日期和时间都会涉及:
jshell> LocalDate.now()
$46 ==> 2018-07-07 jshell> LocalDate.of(2018, 3, 30)
$47 ==> 2018-03-30 jshell> LocalTime.now()
$48 ==> 00:32:06.883656 jshell> LocalTime.of(12,43,12,33333);
$49 ==> 12:43:12.000033333 jshell> LocalDateTime.now()
$50 ==> 2018-07-07T00:32:30.335562400 jshell> LocalDateTime.of(2018, 12, 30, 12,33)
$51 ==> 2018-12-30T12:33 jshell> LocalDateTime.of(LocalDate.now(), LocalTime.now())
$52 ==> 2018-07-07T00:40:38.198318200
而ZoneDateTime会带有时区和偏移量的,可以看看他的成员变量:
/**
* The local date-time.
*/
private final LocalDateTime dateTime;
/**
* The offset from UTC/Greenwich.
*/
private final ZoneOffset offset;
/**
* The time-zone.
*/
private final ZoneId zone;
可以看出这是集合了时间时区和偏移量的新的时间类。
二、常用的TimeUtils或者DateUtils的编写
import java.text.ParseException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map; /**
* Created by hehuaichun on 2018/10/22.
*/
public class TimeUtils {
/**
* 考虑港股和美股 采用GMT-1时区来确定报表日 即T日的报表包含北京时间T日9时至T+1日9时的数据
*/
public static final ZoneId TIMEZONE_GMT_1 = ZoneId.of("GMT-1");
public static final String TIMEZONE_EST_NAME = "US/Eastern";
public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
public static final String TIMEZONE_GMT8_NAME = "GMT+8";
public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME); /**
* 常用时间转换格式
*/
public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_NO_GAP_FORMAT = "yyyyMMdd";
public static final String DATE_GAP_FORMAT = "yyyy-MM-dd";
public static final String TIME_HH_MM_FORMAT = "HHmm";
public static final Map<String, DateTimeFormatter> DATE_TIME_FORMAT_MAP = new Hashtable<String, DateTimeFormatter>() {
{
put(TIME_FORMAT, DateTimeFormatter.ofPattern(TIME_FORMAT));
put(DATE_NO_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_NO_GAP_FORMAT));
put(DATE_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_GAP_FORMAT));
put(TIME_HH_MM_FORMAT, DateTimeFormatter.ofPattern(TIME_HH_MM_FORMAT));
}
}; /**
* 根据format的格式获取相应的DateTimeFormatter对象
*
* @param format 时间转换格式字符串
* @return
*/
public static DateTimeFormatter getDateTimeFormatter(String format) {
if (DATE_TIME_FORMAT_MAP.containsKey(format)) {
return DATE_TIME_FORMAT_MAP.get(format);
} else {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
DATE_TIME_FORMAT_MAP.put(format, formatter);
return formatter;
}
} /**
* 获取当前日期的开始时间
*
* @param zoneId 时间偏移量
* @return
*/
public static LocalDateTime todayStart(ZoneId zoneId) {
return startOfDay(0, zoneId);
} /**
* 获取当前的ZoneDateTime
*
* @param zoneId 时区偏移量
* @return
*/
public static ZonedDateTime now(ZoneId zoneId) {
return ZonedDateTime.now(zoneId);
} /**
* 获取当前日期的开始时间ZonedDateTime
*
* @param date 日期
* @param zoneId 时区偏移量
* @return
*/
public static ZonedDateTime localDateToZoneDateTime(LocalDate date, ZoneId zoneId) {
return date.atStartOfDay(zoneId);
} /**
* 获取当前日期的开始时间
*
* @param dateTime
* @return
*/
public static LocalDateTime startOfDay(ZonedDateTime dateTime) {
return dateTime.truncatedTo(ChronoUnit.DAYS).toLocalDateTime();
} /**
* 获取今天后的指定天数的开始时间
*
* @param plusDays 当前多少天后
* @param zoneId 时区偏移量
* @return
*/
public static LocalDateTime startOfDay(int plusDays, ZoneId zoneId) {
return startOfDay(now(zoneId).plusDays(plusDays));
} /**
* 获取指定日期的后几个工作日的时间LocalDate
*
* @param date 指定日期
* @param days 工作日数
* @return
*/
public static LocalDate plusWeekdays(LocalDate date, int days) {
if (days == 0) {
return date;
}
if (Math.abs(days) > 50) {
throw new IllegalArgumentException("days must be less than 50");
}
int i = 0;
int delta = days > 0 ? 1 : -1;
while (i < Math.abs(days)) {
date = date.plusDays(delta);
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
i += 1;
}
}
return date;
} /**
* 获取指定日期的后几个工作日的时间ZoneDateTime
*
* @param date
* @param days
* @return
*/
public static ZonedDateTime plusWeekdays(ZonedDateTime date, int days) {
return plusWeekdays(date.toLocalDate(), days).atStartOfDay(date.getZone());
} /**
* 获取当前月份的第一天的时间ZoneDateTime
*
* @param zoneId
* @return
*/
public static ZonedDateTime firstDayOfMonth(ZoneId zoneId) {
return now(zoneId).withDayOfMonth(1);
} /**
* 将Date转成指定时区的Date
*
* @param date
* @return
*/
public static Date dateToDate(Date date, ZoneId zoneId) {
LocalDateTime dt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
return toDate(ZonedDateTime.of(dt, zoneId));
} /**
* 将LocalDate转成Date
*
* @param date
* @return
*/
public static Date toDate(LocalDate date) {
return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
} /**
* ZonedDateTime 转换成Date
*
* @param dateTime
* @return
*/
public static Date toDate(ZonedDateTime dateTime) {
return Date.from(dateTime.toInstant());
} /**
* String 转换成 Date
*
* @param date
* @param format
* @return
* @throws ParseException
*/
public static Date stringToDate(String date, String format, ZoneId zoneId) throws ParseException {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
Instant instant = Instant.from(formatter.parse(date));
return Date.from(instant);
} /**
* 将Date转成相应的时区的localDate
*
* @param date
* @param zoneId
* @return
*/
public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
return date.toInstant().atZone(zoneId).toLocalDate();
} /**
* 将Instant转成指定时区偏移量的localDate
*
* @param instant
* @param zoneId
* @return
*/
public static LocalDate toLocalDate(Instant instant, ZoneId zoneId) {
return instant.atZone(zoneId).toLocalDate();
} /**
* 将Instant转换成指定时区偏移量的localDateTime
* @param instant
* @param zoneId
* @return
*/
public static LocalDateTime toLocalDateTime(Instant instant, ZoneId zoneId){
return instant.atZone(zoneId).toLocalDateTime();
} /**
* 将Instant转成系统默认时区偏移量的LocalDateTime
* @param instant
* @return
*/
public static LocalDateTime toLocalDateTime(Instant instant){
return toLocalDateTime(instant, ZoneId.systemDefault());
} /**
* 将ZoneDateTime 转成 指定时区偏移量的LocalDateTime
* @param zonedDateTime 时间
* @param zoneId 指定时区偏移量
* @return
*/
public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId){
return zonedDateTime.toInstant().atZone(zoneId).toLocalDateTime();
} /**
*将ZoneDateTime 转成 LocalDateTime
* @param zonedDateTime
* @return
*/
public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime){
return zonedDateTime.toLocalDateTime();
} /**
* String 转成 ZoneDateTime
* 需要类似 yyyy-MM-dd HH:mm:ss 需要日期和时间信息完整信息
*
* @param date
* @param format
* @param zoneId
* @return
*/
public static ZonedDateTime stringToZoneDateTime(String date, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return ZonedDateTime.parse(date, formatter);
} /**
* 将时间戳long转成ZonedDateTime
*
* @param timeStamp
* @param zoneId
* @return
*/
public static ZonedDateTime longToZoneDateTime(long timeStamp, ZoneId zoneId) {
return ZonedDateTime.from(Instant.ofEpochMilli(timeStamp).atZone(zoneId));
} /**
* 两个时区的zoneDateTime相互转换
*
* @param zonedDateTime 需要转换的如期
* @param zoneId 转换成的ZoneDateTime的时区偏移量
* @return
*/
public static ZonedDateTime zonedDateTimeToZoneDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId) {
return ZonedDateTime.ofInstant(zonedDateTime.toInstant(), zoneId);
} /**
* Date 转成 指定时区偏移量的ZoneDateTime
* @param date
* @param zoneId
* @return
*/
public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId){
return date.toInstant().atZone(zoneId);
} /**
* LocaldateTime 转成 指定时区偏移量的ZonedDateTime
* @param localDateTime 本地时间
* @param zoneId 转成ZonedDateTime的时区偏移量
* @return
*/
public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId){
return localDateTime.atZone(zoneId);
} /**
* Date装换成String
*
* @param date 时间
* @param format 转化格式
* @return
*/
public static String dateToString(Date date, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return formatter.format(date.toInstant());
} /**
* ZoneDateTime 转换成 String
*
* @param dateTime
* @param zoneId localDateTime所属时区
* @return
*/
public static String zoneDateTimeToString(ZonedDateTime dateTime, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return dateTime.format(formatter);
} /**
* LocalDateTime 转成 String
*
* @param localDateTime
* @param format
* @return
*/
public static String localDateTimeToString(LocalDateTime localDateTime, String format){
DateTimeFormatter formatter = getDateTimeFormatter(format);
return localDateTime.format(formatter);
} /**
* 将ZonedDateTime转成时间戳long
*
* @parm zonedDateTime
* @return
*/
public static long zoneDateTimeToLong(ZonedDateTime zonedDateTime) {
return zonedDateTime.toInstant().toEpochMilli();
} /**
* 将LocalDateTime转成时间戳long
* @param localDateTime
* @param zoneId
* @return
*/
public static long toLong(LocalDateTime localDateTime, ZoneId zoneId){
return zoneDateTimeToLong(localDateTime.atZone(zoneId));
} }
三、编写的总结
1、首先我们尽量使用线程安全的时间类,也就是说尽量使用java8以后的几种时间类。从源码可以看出,java8之后的几个时间类是用final修饰的,线程安全。
2、我们要利用好静态变量,也就是不仅在使用的全局变量的时候需要线程安全的问题,还需要考虑时间空间的代价。其实SimpleDateTimeFormat和DateTImeFormatter的生成会消耗不少资源。
3、Date类中含有时间戳,Instant有两个成员变量seconds和nanos变量,这样Instant就作为一个中间量,作用很大,不仅提供了java8之前的时间类和java8之后时间类的转换,还提供了其他很多有用的方法。
4、ZonedDateTime含有时区偏移变量,其他的如Date、LocalDateTime、LocalDate、LocalTime、Instant都不含有时区偏移变量,但是Date想要转换成java8之后的时间类或者使用DateTimeFormatter转换时,需要提供ZoneId或者ZoneOffset,类似这种 date.toInstant().atZone(zoneId)。整体思路实现Date -> Instant -> ZonedDateTime -> 其他时间类。
5、ZonedDateTime是有日期和时间的,我们在DateTimeFormatter的时候需要注意, ZonedDateTime转成字符串非常的灵活,但是字符串转成ZonedDateTime的时候需要提供类似yyyy-MM-dd HH:mm:ss这种格式。
java的TimeUtils或者DateUtils的编写心得的更多相关文章
- JAVA第二次作业展示与学习心得
JAVA第二次作业展示与学习心得 在这一次作业中,我学习了复选框,密码框两种新的组件,并通过一个邮箱登录界面将两种组件运用了起来.具体的使用方法和其他得组件并没有什么大的不同. 另外我通过查阅资料使用 ...
- 搭建java开发环境、使用eclipse编写第一个java程序
搭建java开发环境.使用eclipse编写第一个java程序 一.Java 开发环境的搭建 1.首先安装java SDK(简称JDK). 点击可执行文件 jdk-6u24-windows-i586. ...
- 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n
35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...
- 用Java为Hyperledger Fabric(超级账本)编写区块链智能合约链代码
编写第一个 Java 链代码程序 在上一节中,您已经熟悉了如何构建.运行.部署和调用链代码,但尚未编写任何 Java 代码. 在本节中,将会使用 Eclipse IDE.一个用于 Eclipse 的 ...
- JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识
JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...
- 文章推荐一个Java程序员跟大家谈谈从业心得
一个Java程序员跟大家谈谈从业心得 2017-10-21 java那些事 java那些事 java那些事 微信号 csh624366188 功能介绍 分享java开发中常用的技术,分享软件开发中各种 ...
- Java初学者最近三次作业的心得体会
作为一个初学者,简单的谈一下自己的作业心得体会.如果你是完全没有接触过Java的学习,本篇博文可能会有些收获,如果你已经学习Java有一段时间了,那么可以放弃这篇文章了,因为这篇文章讲解的是基本的东西 ...
- java web学习总结(二十三) -------------------编写自己的JDBC框架
一.元数据介绍 元数据指的是"数据库"."表"."列"的定义信息. 1.1.DataBaseMetaData元数据 Connection.g ...
- 使用JAVA客户端对HDFS进行代码编写(五)
在linux中,在JAVA中编程,耗时的不是代码的编写而是环境的搭建,版本的选择...日了苍天,昨天eclipse突然抽风在linux运行不起来,耗了几个小时,试了各种办法...现在windows环境 ...
随机推荐
- img src加载失败给默认图片(默认图片加载失败不冒泡)
Jquery方法 $("img").one("error", function(e){ $(this).attr("src", " ...
- 【openwrt+arduion】案例
http://www.geek-workshop.com/thread-4950-1-1.html http://www.guokr.com/article/319356/ http://www.gu ...
- 漫游kafka实战篇之搭建Kafka开发环境(3)
上篇文章中我们搭建了kafka的服务器,并可以使用Kafka的命令行工具创建topic,发送和接收消息.下面我们来搭建kafka的开发环境. 添加依赖 搭建开发环境需要引入kafka的jar包 ...
- spring boot 启动数据库报错(Exception during pool initialization.)
2018-06-27 14:12:28.804 ERROR 14312 --- [ restartedMain] com.zaxxer.hikari.pool.HikariPool : HikariP ...
- 部署全局ajax处理
$.ajaxSetup({ beforeSend:function(){ $('.loading').show(); }, complete:function(){ $('.loading').fad ...
- SmartGit Mac、Liunx、Windows过期后破解方法
根据自己的操作系统,进入相应的文件夹 ,可能还有一个版本号的文件夹,再进入 Windows: %APPDATA%\syntevo\SmartGit\ OS X: ~/Library/Preferenc ...
- 【BZOJ4808/3175】马/[Tjoi2013]攻击装置 最小割
[BZOJ4808]马 Description 众所周知,马后炮是中国象棋中很厉害的一招必杀技."马走日字".本来,如果在要去的方向有别的棋子挡住(俗称"蹩马腿" ...
- ios UITableView高度自适应(转)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // ...
- 在Ubuntu上搭建hive环境
一.准备软件 二.安装虚拟机 1.新建虚拟机向导 2.安装客户机操作系统 3.用户名密码设置 4.设置虚拟机名称和保存位置 5.处理器设置 6.设置虚拟机内存 7.然后一直next下去(有的根据自己的 ...
- 解决存储过程中拼接的SQL字符串超长导致sql语句被截取的问题
今天遇到了一个奇葩的问题:存储过程中的sql字符串拼接的太长,超出了分页存储过程执行sql参数的nvarchar(4000)的长度. 没办法,只能修改自己的存储过程,因为分页存储过程是不能动的. 开始 ...