缘起:在一节视频中,有这么一段留言:“会不会推出SpringBoot整合Mybaits配置文件sqlMapConfig.xml搭配mapper.xml的视频呢??? 看到有这个整合直接付款来看,结果是急速开发模式,sql都写在类中了,想看配置方式的 ,大神出一个吧。”粉丝需求,那才是真的需求。
来源:MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
介绍:MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。
在Spring Boot中使用注解集成MyBatis的话,那么核心的文件就是实体类和SQL的映射类,比如DemoMapper,在此类当中就是方法和对应的注解sql语句,那么要怎么能够识别到DemoMapper类呢,在Spring Boot中就特别的简单,在启动类App中加入一个注解@MapperScan(指定Mapper包路径)。
org.springframework.boot
spring-boot-starter-parent
1.4.1.RELEASE
@SpringBootApplication
@MapperScan("com.kfit.*.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
public class Demo {
private int id;
private String name;
//省略getter and setter
}
public interface DemoMapper {
@Insert("insert into Demo(name) values(#{name})")
public void save(Demo demo);
}
@Service
public class DemoService {@Autowired
private DemoMapper demoMapper;
@Transactional//添加事务.
public void save(Demo demo){
demoMapper.save(demo);
}
}
@RestController
public class DemoController {
@Autowired
private DemoService demoService;
@RequestMapping("/save")
public Demo save(){
Demo demo = new Demo();
demo.setName("张三");
demoService.save(demo);
return demo;
}
}
########################################################
###datasource -- mysql的数据库配置.
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10
好了,到这里就可以启动App.java进行访问测试了,访问地址:
http://127.0.0.1:8080/save在访问之前需要注意:
(1)确保创建了数据库test;
(2)确保创建了表demo,建表语句如下:
CREATE TABLE demo (
id int NOT NULL AUTO_INCREMENT ,
name varchar(100) NULL ,
PRIMARY KEY (id)
);
访问之后,在浏览器端会看到数据:
{"id":0,"name":"张三"}
在上一篇文章中,我们已经会集成MyBatic并且完成了保存数据的动作,但是现在如果你细心观察的话,在浏览器看到的数据中id=0。有人说:我不需要返回id呀,id返回我也用不到,返回为0的话,无所谓了。但是在实际项目中,我们是有很多场景需要用到返回的id的
场景
(2)场景2:在题库管理的时候,我们需要录入题目信息以及题库的选项,对于题库的选项是可以多个,如下:
题目:你最喜欢的是技术是?
A: Java语言 B: PHP语言 C: python语言 D:C语言
那么对于题目信息我们会保存到一张表中Question,对于选项,我们会保存到另外一张表QuestionOption,对于表QuestionOption会有一个外键qid,也就是question的主键。对于Question和QuestionOption的保存是在同一个请求就完成的(如果是题目的保存和选项的保存是两个请求才完成的,那么流程不一样)。在保存QuestionOption的时候,需要用到Question的主键,这时候后台的保存代码是这样的:
Question question = Question();
int qid = save(Question);
QuestionOption qo = new QuestionOption();
qo.setQid(qid);
int qid = save(QuestionOption);
public interface DemoMapper {
@Insert("insert into Demo(name) values(#{name})")
@Options(keyProperty="id",keyColumn="id",useGeneratedKeys=true)
public void save(Demo demo);
}
在xml编写的时候,看看能不能加
3、@Options解说
@Options注解中的工作就比较有意思,我们在插入记录时,一般是定义主键自增(auto_increment),但是在某些情况下,我们插入一条记录后,还想得到这条记录的自增主键ID,useGeneratedKeys=true就是定义数据库返回主键ID的,常用的属性如下:
useCache=true,
flushCache=false,
resultSetType=FORWARD_ONLY,
statementType=PREPARED,
fetchSize= -1,timeout=-1 ,
useGeneratedKeys=false ,
keyProperty=”id“。
KeyProperty是实体类中定义的主键字段名;
KeyColumn是表中主键对应的字段;
useGeneratedKeys=true定义数据库返回主键ID;
注解中的useCache还可以设置缓存相关的选项:
useCache = true表示本次查询结果被缓存以提高下次查询速度,flushCache = false表示下次查询时不刷新缓存,timeout = 10000表示查询结果缓存10000秒。
参考spring-boot-mybatis
在原先的代码基础上进行编码,在DemoMapper类中加入:
@Update("update Demo set name=#{name} where id=#{id}")
public int update(@Param("id")int id,@Param("name")String name);使用@Update标明这是一个update语句,注意:在这里返回了int值,这个是值返回成功修改的数量,比如:成功找到了5条数据,并且修改成功了,那么返回值就是5。
@Delete("delete from Demo where id=#{id}")
public int delete(int id);
@Select("select *from Demo")
public ListselectAll();
@Select("select *from Demo where id=#{id}")
public Demo selectById(int id);
参考spring-boot-mybatis
MyBatis提供了拦截器接口,我们可以实现自己的拦截器,将其作为一个plugin装入到SqlSessionFactory中。
PageHelper是Github上有位开发者写了一个分页插件,可以很方便的添加到MyBatis的拦截器接口中。
com.github.pagehelper
pagehelper
4.1.0
@Configuration
public class MyBatisConfiguration {
/**
* 注册MyBatis分页插件PageHelper
* @return
*/
@Bean
public PageHelper pageHelper() {
System.out.println("MyBatisConfiguration.pageHelper()");
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
这个使用起来特别的简单,只是在原来查询全部的代码之前加入一句:
PageHelper.startPage(1,2);
第一个参数是第几页;第二个参数是每页显示条数。
参考spring-boot-mybatis
在MyBatis中集成了PageHelper,然后也在需要使用分页的地方加入了如下代码:
PageHelper.startPage(1,2);
1、需要在pom.xml文件中添加PageHelper依赖;
2、需要配置一个MyBatisConfiguration -->注入了PageHelper;
3、需要在需要分页的查询前面使用PageHelper.startPage(pageNum,pageSize);分页
这个可能你使用错了版本号,主要是pom.xml文件中的版本的引入,错误的版本引入:
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.0.0
我在博客中已经写的很详细了,但是还是有人会掉进坑里,之所以会有这篇文章的出现就是因为已经有人已经掉进坑里了。那么正确的配置是:
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
请不要使用1.0.0版本,因为还不支持拦截器插件,
1.3.0 是博主写帖子时候的版本,大家使用最新版本即可
第二种不好使的情况就是重新定义了SqlSessionFactory但是并没有配置对应的PageHelper插件,所以导致使用PageHelper.startPage(1,1); 无效,那么如果要重新定义SqlSessionFactory 的话
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Interceptor[] plugins = new Interceptor[]{pageHelper()};
sqlSessionFactoryBean.setPlugins(plugins);
// 指定mybatisxml文件路径
sqlSessionFactoryBean.setMapperLocations(resolver
.getResources("classpath:/mybatis/*.xml"));
return sqlSessionFactoryBean.getObject();
}
参考spring-boot-mybatis
异常信息如下:
nested exception is org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
出现这个异常的原因是因为你在使用@insert的使用,没有进行相应的字段对应关系。
如下的代码就会报如上的错误:
@Insert("insert into Demo(name,password) values(#{name},#{password})")
public void save(String name,String password);
正确的代码应该如下:
@Insert("insert into Demo(name,password) values(#{name},#{password})")
public void save(@Param("name") String name,@Param("password") String password);
单参数特殊情况
但是如下的代码就不会报错:
@Insert("insert into Demo(name,password) values(#{name})")
public void save(String name);
当 insert 语句中只有一个参数的,对应的void save方法不需要做任何特殊处理(不需要加@Param也是可以对应上的),当有多个参数的时候,需要使用@Param注解进行字段的对应。
参考spring-boot-mybatis
#、$符号在MyBatis的SQL参数注入有很重要的作用,这两个符号还是需要理解清楚的,不然可能就会写出被攻击的SQL语句了。好了,这篇文章就是为了解决#、$各自的使用场景。
使用#{}意味着使用的预编译的语句,即在使用jdbc时的preparedStatement,sql语句中如果存在参数则会使用?作占位符,我们知道这种方式可以防止sql注入,并且在使用#{}时形成的sql语句,已经带有引号,例,select * from table1 where id=#{id} 在调用这个语句时我们可以通过后台看到打印出的sql为:select * from table1 where id='2' 加入传的值为2.也就是说在组成sql语句的时候把参数默认为字符串。
使用${}时的sql不会当做字符串处理,是什么就是什么,如上边的语句:select * from table1 where id=${id} 在调用这个语句时控制台打印的为:select * from table1 where id=2 ,假设传的参数值为2。
从上边的介绍可以看出这两种方式的区别,我们最好是能用#{}则用它,因为它可以防止sql注入,且是预编译的,在需要原样输出时才使用${},如,
select * from ${tableName} order by ${id} 这里需要传入表名和按照哪个列进行排序 ,假如传入table1、id 则语句为:select * from table1 order by id
如果是使用#{} 则变成了select * from 'table1' order by 'id' 我们知道这样就不对了。
使用#{}的语句:
@Select("Select * from Demo where name = #{name}")
public List
说明:这个例子创建一个预编译的语句,看起来像:select * from Demo where name = ?;
观察控制台的SQL打印:
selectByName1 : ==> Preparing: Select * from Demo where name = ?
DemoMapper.selectByName1 : ==> Parameters: 王五(String)
DemoMapper.selectByName1 : <== Total: 9
使用${}的语句:
@Select("Select * from Demo where name = '${name}'")
public List
说明:这个例子创建一个内联的语句,看起来像: select * from Demo where name = '王五';
观察控制台的打印:
DemoMapper.selectByName2 : ==> Preparing: Select * from Demo where name = '王五'
DemoMapper.selectByName2 : ==> Parameters:
DemoMapper.selectByName2 : <== Total: 9
@Select("select *from where id=#{id}")
public Demo select3(int id);
@Select("select *from demo order by ${orderField}")//原样替换.
public Demo select4(String orderField);
@Select("select *from demo order by ${orderField} ${ascOrDesc}")//原样替换.
public Demo select5(@Param("orderField") String orderField, @Param("ascOrDesc") String ascOrDesc);
参考spring-boot-mybatis
@Result 修饰返回的结果集,
如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
这个注解相当于XML配置文件中的。
@Select("select *from Demo where id=#{id}")
public Demo selectById(int id);
比如Demo实体类:
private int id;
private String name;
private Date updateTime;数据库表信息:
id int
name varchar
update_time datetime@Select("select *from Demo where id=#{id}")
@Results({
@Result(property="updateTime",column="update_time")
})
public Demo selectById2(int id);
@Select("select *from Demo where id=#{id}")
@Results({
@Result(property="updateTime",column="update_time"),
@Result(property="sexEnum",column="sex_enum",javaType=SexEnum.class)
})
public Demo selectById2(int id);
@Select 是查询类的注解,所有的查询均使用这个
@Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
@Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
@Update 负责修改,也可以直接传入对象
@delete 负责删除
参考spring-boot-mybatis
网友1:这样如果是不定条件查询怎么解决呢?也就是xml中可以用if标签,注解怎么搞呢?
网友2:这动态sql用注解的方式不太方便吧!博主有好方法,请推出参照一下,一直没用注解的原因,就是动态sql没有好的方式,包括when foreach!针对简单的查询还是可以的!
在一个表中有id,name,email我们查询的时候,希望name不为null的时候,作为查询条件,如果email不为null的时候,作为查询条件。
@Select("Select * from Demo where name =#{name} and email=#{email} ")
public List
现在我们希望的是如果name不为null的话,那么就当做条件,否则就不要当做条件;如果email不为null,那么就当做条件,否则不当做条件
那么方案一:使用
@Select(" ")
public Listselect4(Demo demo);
参考spring-boot-mybatis
在上一个视频中,我们使用了