1.MyBatis vs JPA:
MyBatis的优势:
1.SQL语句可以自由控制,更灵活,性能较高
2.SQL与代码分离,易于阅读和维护
3.提供XML标签,支持编写动态SQL语句
JPA的优势:
1.JPA移植性比较好(使用JPQL语句)
2.提供了很多CRUD方法、开发效率高
3.对象化程度更高
MyBatis的劣势:
1.简单的CRUD操作还得写SQL语句
2.XML中有大量的SQL要维护
3.MyBatis自身功能很有限,但是支持plugin
为了解决我们的MyBatis的劣势,MyBatis-plus隆重登场
2.MyBatis-plus简介
MP是一个mybatis的增强工具,只做增强不做改变,作者的愿景是成为mybatis的好基友TaT.
4.MyBatis-plus的特性:
1.无侵入,损耗小,强大的CRUD操作
2.支持Lambda形式调用,支持多种数据库
3.支持主键自动生成,支持ActiveRecord模式
4.支持全局自定义通用操作,支持关键字自动转义
5.内置代码生成器,内置分页插件,内置性能分析插件
6.内置全局拦截插件,内置Sql注入剥离器(防止sql注入攻击)
5.MyBatis-plus的快速入门:
见官方文档学习之快速入门
tips:
1.pom.xml文件中有些坐标我们没有写版本号,是因为我们在spring-boot-starter中已经引入了。
2.在application.yml文件中对数据库url的配置项中,后面的参数useSSL是使用安全套接字的意思,一般我们项目在本地启动用来自己测试的话填false,serverTimezone表示时区,GMT%2B8代表的是格林尼治时间的东八区。“%2B”=”+“。
3.在dao包中的Mapper接口中,如果我们想使用MP的方法,我们需要继承BaseMapper
4.在测试类中,我们添加@SpringBootTest注解,标识该类基于Springboot测试运行,
运行器—@RunWith(SpringRunner.class)标志该类可以基于spring环境运行junit测试。
5.在测试中类中,
通过MP的内置方法获得userlist:
List <User> list = userMapper.selectList(null);
使用断言来判断一下实际获得的记录数是不是和预期的相同:
Assert.assertEquals(5,list.size());
迭代输出:
list.forEach(System.out::println);
1.SSM传统编程模式:
接口中写抽象方法->XML或注解写SQL->Service中调用接口->Controller中调用Service
2.MP中的通用mapper:
tips:
日志输出的配置:
logging:
level:
root:warn
com.mp.dao:trace
pattern:
console:'%p%m%n'
level代表日志输出的等级,优先级从高到低依次为:OFF FATAL ERROR WARN INFO DEBUG TRACE ALL
*
如果将log level设置在某一个级别上 那么比此级别优先级高的log都能打印出来
例如 如果设置优先级为WARN 那么OFF FATAL ERROR WARN 4个级别的log能正常输出
而INFO DEBUG TRACE ALL级别的log则会被忽略
pattern(格式)中的console表示输出的内容,其中p%、m%、n%分别表示日志级别、日志内容、换行。
1.新增方法:
编辑要插入的实体:
User u = new User;
u.setName("刘明强");
u.setAge(31);
u.setManagerId(13217264178378578L);
u.setCreateTime(LocalDateTime.now());
用MP插入:
int rows = userMapper.insert(u);
//返回影响记录数
MP执行插入或者修改操作时,如果某个字段的值null,那么在SQL中不会出现该属性。
2.常用注解:
我们会碰到一种情况,就是我们的实体类名按照驼峰原则去匹配不能自动匹配到数据库中的表,比如我们公司中要求一些表具有前缀(sys_user),这个时候我们可以在实体类中使用注解 @TableName(“sys_user”) 来指定我们这个实体类映射的表。MP默认采用雪花算法帮我们自动填充主键id,当我们数据中的主键字段名有特殊要求,实体类中的属性不能自动映射时,我们可以在 实体类的成员变量上添加 @TableId来声明此成员变量对应数据库中的主键。数据库中的普通字段按照我们的规则不能匹配时,我们可以在想要映射的实体类属性上使用 @TableFiled(“字段名”) 来显示的进行匹配。
3.排除非表字段的三种方式:
当我们遇到这样的场景:实体类中的某个成员变量不对应数据库表中的字段时,MP提供了三种方式来取消这个成员变量的映射:
1).使用transient来修饰的成员变量不参与序列化过程:
private transient String remark;
2).当我们需要序列化时,我们可以把它标注为静态的成员变量:
private static String remark;
3).当我们需要序列化,又不想把它设置成静态变量时,我们可以使用MP提供的注解:
@TableFiled(exist=false)
private String remark;
BaseMapper中提供了一系列的查询方法:
1.基本查询方法
1)T selectById(Serializable id)----根据主键id查询
User user = userMapper.selectById(13274812348173284712L);
System.out.println(user);
2)List
List<long> idsList = Array.asList(413241341325L,613472617283657L,23647671513278L);
List<user> userList = userMapper.selectBatchIds(idList);
userList.forEach(System.out::println);
3)List
//map.put("name","王天风")
//map.put("age",25)
//生成的查询语句:where name="王天风" and age=30
Map <String,Object> columnMap = new HashMap<>();
columnMap.put("name","王天风");
columnMap.put("age",25);
List<User> userList = userMapper.selectByMap(columnMap);
注意:这里的name和age都是数据库中的列名,不是实体类中的变量名!!!!
2.以条件构造器为参数的查询方法
需要以wrapper作为参数,MP提供了强大的条件构造器wrapper
1)查询名字包含雨且年龄小于40(name like ‘雨’ and age < 40)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//QueryWrapper query = Wrappers.query();
//两种方式都可以创建user的wrapper
queryWrapper.like("name","雨").lt("age",40);//链式查询
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
2)名字中包含雨并且年龄大于等于20小于等于40并且email不为空(name like ‘%雨%’ and age between 20 and 40 and email is not not null)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","雨").between("age",20,40).isNotNull("email");
//我们用点来连接链式查询的条件,这里的"."就是SQl中的"and"
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
3)名字为王姓或者年龄大于等于25,按照年龄降序排列,年龄相同按照id升序排列(name like ‘王%’ or age>=25 order by age desc,id asc)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name","王").or().ge("age",25).orderByDesc("age").orderByAsc("id");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
4)创建日期为2019年2月14日并且直属上级为名字为王姓(date_format(create_time,’%Y-%m-%d’) and manage_id in (select id from user where name like ‘王%’))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//date_format(create_time,'%Y-%m-%d')='2019-02-14'这种写法会被sql注入
queryWrapper.apply("date_format(create_time,'%Y-%m-%d')={0}","2019-02-14")
.inSql("manager_id","select id from user where name like '王%'");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
5)名字为王姓并且(年龄小于40或邮箱不为空)----name like ‘王%’ or (age<40 and age >20 and email is not null)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name","王").and(qw -> qw.lt("age",40).or().isNotNull("email"))
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
6)名字为王姓或者(年龄小于40并且年龄大于20并且邮箱不为空)----name like’王%’ or (age < 40 and age >20 and email is not null)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name","王").or(qw -> qw.lt("age",40).gt("age",20).isNotNull("email"));
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
7)(年龄小于40或者邮箱不为空)并且名字为王姓 ----(age < 40 or email is not null)and name like ‘王%’
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.nested(qw -> qw.lt("age",40).or().idNotNull("email"))
.likeRight("name","王");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
8)年龄为30、31、34、35-----age in (30、31、34、35)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("age",Array.asList(30,31,34,35))
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
9)返回只满足条件的一条语句----limit 1
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("age",Array.asList(30,31,34,35)).last("limit 1")
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
注意:这种方式谨慎使用,特别是用户自己传参的情况下,有sql注入的风险。
3.select中字段不全出现的处理方法
10)查询名字包含雨且年龄小于40(需求1加强版)
~1.我们只要id和name
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id","name").like("name","雨").lt("age",40);//链式查询
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
~2.我们排除create_time和manager_id
queryWrapper.like("name","雨").lt("age",40)
.select(User.class,info->!info.getColumn().equals("create_time")&&
!info.getColumn().equals("manageer_id"));
4.条件构造器中condition的作用
当我们在传入不定的查询条件时,我们原本要去判断它是否为空:
private void codition(String name,String email){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if(StringUtil.isNotEmpty(name)){
queryWrapper.like("name",name)
}
if(StringUtil.isNotEmpty(email)){
queryWrapper.like("email",email)
}
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
@Test
public void testCondition(){
String name = "王";
String email = "";
condition(name,email)
}
我们可以用带condition的like去替换上面的两次判空,控制条件是否加入到where语句中:
queryWrapper.like(StringUtil.isNotEmpty(name),"name",name)
.like(StringUtil.isNotEmpty(email),"email",email)
5.创建条件构造器时传入实体对象
当我们在条件构造器中传入实体时,实体的非空属性会加入到where条件中,实体生成的条件和别的条件语句互不干扰,要谨慎使用。
@Test
public void selectByWrapperEntity(){
User whereUser = new User();
whereUser.setName("刘雨红");
whereUser.setAge(30);
QueryWrapper<User> queryWrapper = new QueryWrapper<User>(whereUser);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
当我们允许实体进行传输时,我们可以在实体类的变量上加入@TableField(condition=""),condition后填入删选条件,我们可以使用MP帮我们提供的筛选条件(SqlCondition.xx),也可以自定义筛选条件(eg. “%s<#{%s}”----表示小于)。
6.条件构造器中allEq的用法
@Test
public void selectByWrapperAllEq(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
Map<String,Obeject> params = new HashMap<>();
params.put("name","王天风");
params.put("age",25);
queryWrapper.allEq(params);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
注意:在queryWrapper.allEq(params)中,我们可以重载queryWrapper.allEq(params,false);后面计入false参数时,我们的查询值为null的时候这个值会被忽略。我们还可以重载queryWrapper.allEq(filter,params),其中filter是一个函数式接口,我们用lambda表达式传入queryWrapper.allEq((k,v)->!k.equals(“name”),params),此时name变量将被过滤掉,不加入where查询的条件。也有condition的重载,上面已经进行演示。
7.其他以条件构造器为参数的查询方法
返回类型为Map的结果----selectMaps
@Test
public void selectByWrapperMaps(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.like("name","雨").lt("age",40);
List<Map<String,Object>> userList = userMapper.selectMaps(queryWrapper);
userList.forEach(System.out::println);
}
需求:按照直属上级分组,查询每组的平均年龄、最大年龄、最小年龄。并且只取年龄总和小于500的组。(select avg(age) avg_age,min(age) min_age,max(age) max_age from user group by manager_id having sum(age) < 500)
@Test
public void selectByWrapperMaps2(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.select("avg(age) avg_age","min(age) min_age","max(age) max_age")
.groupBy("manager_id".having("sum(age)<0",500))
List<Map<String,Object>> userList = userMapper.selectMaps(queryWrapper);
userList.forEach(System.out::println);
}
只返回第一列的数组----selectObjs
@Test
public void selectByWrapperObjs(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.like("name","雨").lt("age",40);
List<Map<String,Object>> userList = userMapper.selectObjs(queryWrapper);
userList.forEach(System.out::println);
}
返回根据条件查询的总记录数----selectCount
@Test
public void selectByWrapperCount(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.like("name","雨").lt("age",40);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println("总记录数"+count)
}
返回一条记录----selectOne
@Test
public void selectByWrapperOne(){
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.like("name","雨").lt("age",40);
User user = userMapper.selectOne(queryWrapper);
System.out.println(user)
}
注意:查询语句只能返回一个结果或者空,返回两个或者以上时会报错
7.lambda条件构造器
@Test
public void selectLambda(){
//我们一共有以下三种方式创建lambda条件构造器
//LambdaQueryWrapper lambdaQuery = new QueryWrapper().lambda();
//LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper();
LambdaQueryWrapper<User> lambdaQuery = Wrappers.<User>lambdaQuery();
lambdaQuery.like(User::getName,"雨").lt(User::getAge,40);
//where name like '%雨'
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
用这种反式,我们可以防止误写(我们容易写错数据库中的字段名,用这种方式如果书写错误会编译报错)
//(年龄小于40或者邮箱不为空)并且名字为王姓 ----
//(age < 40 or email is not null)and name like '王%'
@Test
public void selectLambda2(){
LambdaQueryWrapper<User> lambdaQuery = Wrappers.<User>lambdaQuery();
lambdaQuery.likeRight(User::getName,"王")
.and(lqw->lqw.lt(User::getAge,40).or().isNoyNull(User::getEmail));
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
3.0.7版本新增的
@Test
public void selectLambda3(){
List<User> userList = new LambdaQueryChainWrapper<User>(userMapper)
.like(User::getName,"雨").ge(User::getAge,20).list();
userList.forEach(System.out::println);
}
实现了ChainQuery
8.使用条件构造器的自定义SQL(3.0.7版本以后)
我们可以用注解或者配置文件的方式自定义的SQL加上条件构造器来组成我们的查询语句,此时我们这样写SQL:select * from user ${ew.customSqlSegment}
9.分页
Mybatis的分页介绍:
采用的是逻辑(内存)分页,把符合条件的数据全查出来到内存中,返回你需要的那个部分,当数据量极大时,会消耗大量内存且查询速度很慢。所以我们需要物理分页
MP分页插件实现物理分页:
首先我们在配置类中注入一个PaginationInterceptor到容器中,就是我们的分页插件
@Configuration
public class MybatisPlusConfig{
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
进行测试
@Test
public void selectPage(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age",26);
Page<User> page = new Page<User>(1,2);
//第一种分页方法
IPage<User> iPage = userMapper.selectPage(page,queryWrapper);
//另一种分页方法
IPage<Map<Strring,Object>> iPage = userMapper.selectMapsPage(page,queryWrapper);
System.out.println("总页数" + iPage.getPages());
System.out.println("总记录数" + iPage.getTotal());
List<User> userList = ipage.getRecords();
userList.forEach(System.out::println)
}
注意:new Page(1,2)可以重载new Page(1,2,false),我们传入的第三个false参数代表不需要查总记录数,需要查询多表时,我们需要在xml中配置sql语句。
1.根据id更新
@Test
public void updateById(){
User user = new User();
user.setId(8934798132748129378957817589);
user.setAge(26);
user.setEmail("[email protected]");
int rows = userMapper.updateById(user);
sout("影响记录数"+rows);
}
2.以条件构造器为参数的更新方法
传入两个参数,一个entity,一个wrapper,entity中的值出现在set语句中,wrapper的条件加在where语句中
@Test
public void updateByWrapper(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<user>();
updateWrapper.eq("name","李艺伟").eq("age",28);
User user = new User();
user.setAge(26);
user.setEmail("[email protected]");
int rows = userMapper.update(user,updateWrapper);
sout("影响记录数"+rows);
}
3.条件构造器中set方法使用
当我们的实体有较多的属性但是我们只需要更新其中一小部分时,我们不需要去new一个实体,直接在Wrapper中调用set方法
@Test
public void updateByWrapper2(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<user>();
updateWrapper.eq("name","李艺伟").eq("age",28).set("age",30);
int rows = userMapper.update(user,updateWrapper);
sout("影响记录数"+rows);
}
lambda
@Test
public void updateByWrapperLambda(){
LambdaUpdateWrapper<User> lambdaUpdate = Wrapper.<User>lambdaUpdate();
lambdaUpdate.eq(User::getName,"李艺伟").eq(User::getAge,30).set(User::getAge,31);
int rows = userMapper.update(user,lambdaUpdate);
sout("影响记录数"+rows);
}
链式lambda修改
@Test
public void updateByWrapperLambdaChain(){
boolean update = new LambdaUpdateChainWrapper<User>(userMapper)
.eq(User::getName,"李艺伟").eq(User::getAge,30).set(User::getAge,32).update();
}
1.根据id删除的方法
@Test
public void deleteById(){
int rows = userMapper.deleteById(328748196356124367L);
sout("删除条数:"+rows);
}
2.其他普通删除方法
@Test
public void deleteByMap(){
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("name","向后");
columnMap.put("age",25);
int rows = userMapper.deleteByMap(columnMap);
sout("删除条数:"+rows);
}
@Test
public void deleteBatchIds(){
int rows = userMapper.deleteBatchIds(Arrays.asList(4173246812367L,47128364723867L,934751782387645L));
sout("删除条数:"+rows);
}
3.以条件构造器为参数的删除方法
@Test
public void deleteByWrapper(){
LambdaQueryWrapper<User> lambdaQuery = Wrappers.<User>lambdaQuery();
lambdaQuery.eq(User::getAge,27).or().gt(User::getAge,41);
int rows = userMapper.delete(lambdaQuery);
sout("删除条数:"+rows);
}
AR探索
1.AR模式简介
AR模式即Active Record模式,是一个对象-关系映射(ORM)技术。每个AR 类代表一张数据表(或视图),数据表(或视图)的字段在 AR 类中体现为类的属性,一个AR 实例则表示表中的一行。AR一直广受动态语言的喜爱,java作为准静态语言一直只能望而叹其优雅,MP的作者在AR的道路上进行了一定的探索,简单来说就是通过实体类对象直接进行表的增删改查操作,方便开发。
2.MP中AR的实现
在MP中实现AR模式我们需要满足两个条件:1.实体类继承抽象类Model,此时如果我们使用了lombok的@Date注解,我们会看到警告提示你没有生成父类成员变量的get和set方法,我们只要在类上再添加@EqualsAndHashCode(callSuper=false)即可,再生成一个serialVersionUID。2.必须存在原始mapper接口并且继承BaseMapper
public class ARTest{
@Test
public void insert(){
User user = new User();
user.setName("刘花");
user.setAge(29);
user.setEmail("[email protected]");
user.setManagerId(3276417267671256L);
user.setCreateTime(LocalDateTime.now());
boolean insert = user.insert();
sout(insert);
}
@Test
public void selectById(){
User user = new User();
User userSelect = user.selectById(4173286781265712L);//查询出的是一个新对象
sout(userSelect);
}
@Test
public void selectById2(){
User user = new User();
user.setId(4173286781265712L);
User userSelect = user.selectById();//查询出的是一个新对象
sout(userSelect);
}
@Test
public void updateById(){
User user = new User();
user.setId(4173286781265712L);
user.setName("张草草");
boolean updateById = user.updateById();
sout(updateById);
}
@Test
public void DeleteById(){
User user = new User();
user.setId(4173286781265712L);
boolean dateById = user.deleteById();
//注意:删除不存在的在逻辑上属于成功,返回true
sout(dateById);
}
@Test
public void insertOrUpdate(){
User user = new User();
user.setName("张强");
user.setAge(29);
user.setEmail("[email protected]");
user.setManagerId(3276417267671256L);
user.setCreateTime(LocalDateTime.now());
//如果id在表中已经有了,执行update,否则执行insert
boolean insertOrUpdate = user.insertOrUpdate();
sout(insertOrUpdate);
}
}
1.MP支持的主键策略简介
MP支持多种主键策略,默认的全局策略是基于雪花算法的自增id,除了默认策略,你也可以设置其他策略。MP的主键策略定义在IdType这个枚举类中:AUTO(0)----数据库id自增、NONE(1)----该类型为未设置主键类型(会跟随全局策略,使用雪花算法)、INPUT(2)----用户输入id、//下面三种类型,只有当插入对象ID为空,才会自动填充ID_WORKER(3)----数值类型的雪花算法自动填充、ID_WORKER_STR(5)----字符串类型的雪花算法自动填充、UUID----全局唯一uuid。
2.局部主键策略实现
@TableId(type=IdType.AUTO)
private Long id;
@TableId(type=IdType.UUID)
private String id;
3.全局主键策略实现
当我们想一下子为全局实体类设置主键策略时,我们在application.yml配置文件中的mybatis-plus标签下面进行配置:
mybatis-plus:
mapper-locations:
-com/mp/mapper//*
global-config:
db-config:
id-type: uuid
注意:局部策略优先于全局策略
1.基本配置
Spring Boot:
mybatis-plus:
......
configuration:
......
global-config:
......
db-config:
......
详见:官方文档
2.进阶配置
详见:官方文档
3.db策略配置
详见:官方文档
1.基本方法
2.批量操作方法
3.链式调用方法
定义一个UserService接口继承Iservice
定义实现类UerServiceImpl继承ServiceImpl
public class ServiceTest{
@Autowired
private UserService userService
@Test
public void getOne(){
//这如果传入false,结果如果有多个就不会报错,会给出警告,并返回第一个结果。
//如果不传false,默认为true,结果有多个的话会报错,不会返回。
userService.getOne(Wrapper.<User>lambdaQuery().gt(User::getAge,25),false);
sout(getOne);
}
@Test
public void Batch(){
User user1 = new User();
user1.setName("徐丽1");
user1.setAge(28);
User user2 = new User();
user1.setName("徐丽2");
user1.setAge(29);
List<User> userList = Arrays.asList(user1,user2);
boolean saveBatch = userService.saveBatch(userList);
sout(saveBatch);
}
@Test
public void chain(){
userService.LambdaQuery().gt(User::getAge,25).like(User::getName,"雨").list()
.forEach(System.out::println);
}
@Test
public void chain2(){
boolean update = userService
.LambdaUpdate().eq(User::getAge,25).set(User::getAge,26).update();
sout(updaste);
}
@Test
public void chain2(){
boolean remove = userService.LambdaUpdate().eq(User::getAge,25).remove();
sout(remove);
}
}
注意: userService.saveBatch(userList)这个方法可以重载userService.saveBatch(userList,1000),后面可以传入一个数字来指定单条sql插入的数量,MP把批量操作写在Service层的原因是sql长度有限制,海量的数据量单条sql无法执行,就算可以执行也容易引起内存泄露,jdbc连接超时等问题,不同的数据库对于单条批量操作语法不一样,不易于通用,目前的解决方案就是循环预处理,批量提交,虽然性能比单条sql慢,但是可以解决以上问题。