在学习Java的过程,我们可能使用了JDBC来访问数据库获取数据,但是JDBC也有比较大的问题sql语句写在java程序中,需求修改的话则需要修改Java代码sql占位符的拼接传递比较麻烦数据库对象映射成Pojo也比较麻烦 MyBatis这个框架通过对JDBC的封装,开发者只需要按照约定的配置进行配置开发,就可以方便的进行ORM操作 O(Object),R(Relational),M(Mapping):对象关系映射,大白话就是Java对象映射成数据库某张表中的一行记录,一行记录又可映射成一个Java对象开始上车 安装Mybatis,传统的模式自然免不了找到对应的Jar包添加成依赖,但通过Maven工具可以方便的控制项目依赖,需要在pom。xml中添加对应依赖,关于Maven这里不介绍过多,不知道GVC怎么写的可以在mvnrepository。commaven仓库中搜索对应的即可,比如搜索MyBatis,选择对应的版本,复制如图所示的依赖配置到pom。xml中,刷新maven依赖即可下载,这里除了Mybatis还需要下载mysqlconnector,Java链接数据库需要通过驱动,不同厂商有各自的实现 创建一个Maven项目,创建过程忽略 这里以Mysql为例子,在maven依赖中添加如下依赖,一个是mybatis,一个是mysql的驱动dependencygroupIdorg。mybatisgroupIdmybatisartifactIdversion3。5。11versiondependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion8。0。30versiondependency复制代码按照官网提供的配置进行本地化配置先配置一份mybatis的主配置文件下面的配置为最简化的配置,需要替换掉dataSource中的四个标签值,并且这里我选择了创建一个jdbc。properties文件用来管理数据库链接信息,通过properties标签的resource属性进行导入,需要注意文件的存放位置,默认会从resource目录开始找(如果不用这种方式,直接写上一个字符串即可)lt;?xmlversion1。0encodingUTF8?!DOCTYPEconfigurationPUBLICmybatis。orgDTDConfig3。0ENhttp:mybatis。orgdtdmybatis3config。dtdconfigurationpropertiesresourcejdbc。propertiespropertiesenvironmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBCdataSourcetypePOOLEDpropertynamedrivervalue{driver}propertynameurlvalue{url}propertynameusernamevalue{username}propertynamepasswordvalue{password}dataSourceenvironmentenvironmentsmappers!配置对应的表映射文件,先配置,待会会进行配置mapperresourcemapperUserMapper。xmlmappersconfiguration复制代码 jdbc。properties创建,自行替换drivercom。mysql。cj。jdbc。Driverurljdbc:mysql:localhost:3306dbusernamerootpassword12345678复制代码 UserMapper。xml创建,这里需要说明一下,mapper标签中的namespace属性,作用类似于唯一包名,主要用于区分不同mapper文件中,增删改查标签中id相同的情况,如有多个mapper文件,里面的查询标签id一致,但是加上namespace。进行拼接就可以区分。这里主要演示mybatis,对应的表设计可和sql自行处理,这里提供参考(学习Mybatis的使用才是文章内容,不了Mysql和Sql的应该先去了解) lt;?xmlversion1。0encodingUTF8?!DOCTYPEmapperPUBLICmybatis。orgDTDMapper3。0ENhttp:mybatis。orgdtdmybatis3mapper。dtdmappernamespaceuserinsertidinsertUserinsertintotuser(username,password,age,gender)values(ben,123,40,0)insertmapper复制代码 目前resources目录下应该有如下文件 编写一个测试类来验证一个最基础的mybatis使用需要创建一个SqlSessionFactoryBuilder对象通过SqlSessionFactoryBuilder对象的build方法创建SqlSessionFactory对象,需要传入一个InpuStream类型的参数,实际上是指上面编写好的mybatisconfig。xml通过SqlSessionFactory的openSession方法创建SqlSession对象,方法可以接受一个boolean参数,由于配置中配置的是JDBC的事务管理器,所以事务默认是不提交的,可以通过传入true来开启自动提交(可以理解为执行一次增删改就自动提交一次),不传或传入false则表示开启事务(得手动提交)由于需要执行的是插入数据操作,所以调用sqlSession。insert方法(会有对应的update和delete方法等),需要传入一个唯一id,这个id指的就是UserMapper。xml中mapper标签下配置的insert标签的id,如果成功,则会返回影响的数据条数publicclassMain{publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionFactoryBuildersqlSessionFactoryBuildernewSqlSessionFactoryBuilder();SqlSessionFactorybuildsqlSessionFactoryBuilder。build(Resources。getResourceAsStream(mybatisconfig。xml));SqlSessionsqlSessionbuild。openSession(true);如果传入true则开启自动提交SqlSessionsqlSessionbuild。openSession();try{像目前只有一个mapper文件,可以不用加前缀,直接insertUser即可,否则最好加上namespace避免重名intcountsqlSession。insert(user。insertUser);会返回影响的条数if(count1){System。out。println(插入成功成功);}else{System。out。println(插入数据失败);}sqlSession。commit();记得要提交事务}catch(Exceptione){抛出异常时事务回滚if(sqlSession!null){sqlSession。rollback();}}finally{结束时关闭资源if(sqlSession!null){sqlSession。close();}}}}复制代码 当执行程序后,可以看到数据库成功增加了一条记录 关于SqlSessionFactoryBuilder的入参输入流,代码演示使用了mybatis提供的一个工具类,如果你想,也可以替换成任何自己实现的InputStream对象,只要能创建即可newFileInputStream(xxxx);ClassLoader。getSystemClassLoader()。getResourceAsStream(xxxx);实际上,MyBatis提供的方法底层就是通过这个ClassLoader对象进行调用的,原理是从类路径开始查找对应资源。。。任何产出InputStream的方式关于日志配置 日志的输出可以让开发过程更清晰的看到SQL的拼接执行等信息,这里需要十分注意settings标签的顺序位置,错了的话会报错,具体顺序报错信息会提示你propertiesresourcejdbc。propertiespropertiessettingssettingnamelogImplvalueSTDOUTLOGGINGsettingsettingsenvironmentsdefaultdevelopment复制代码 这里使用了mybatis内置的标准日志实现,添加配置后再次执行,就能看到如图所示的日志信息,方便开发调试 亦可配置如SLF4J,Log4j等,其中的区别可能是性能或配置需求等,自行抉择动态插入 上面的实例中,插入语句是写死的值,显然实际开发中对应的值是需要前端将用户注册的参数穿过来,此时可以使用{}进行占位,作用等价于JDBC中的?占位符(底层用的是PreparedStatement没有SQL注入问题)执行对应方法时,传入Map集合传入Pojo对象,但是需要编写对应的getter方法测试insert传入Map 先改写mapper。xml,将原本写死的地方改成{Map的key名}mappernamespaceuserinsertidinsertUserinsertintotuser(username,password,age,gender)values({username},{password},{age},{gender})insertmapper复制代码publicclassMain{publicstaticvoidmain(String〔〕args)throwsIOException{简单的工具类封装了SqlSessionSqlSessionsqlSessionSqlSessionUtil。openSqlSession();MapString,ObjectusernewHashMap();user。put(username,jack);user。put(password,jinwandalaohu);user。put(age,60);user。put(gender,0);intcountsqlSession。insert(user。insertUser,user);if(count1){System。out。println(插入成功);}else{System。out。println(insertfailed);}sqlSession。close();}}复制代码 再次执行,可以看到{}被替换成了?,并且日志输出了每个占位符对应的值和数据类型,并且数据成功插入 需要注意的是,如果使用Map当参数传入,占位符填写了不存在的key,则最终插入的值会是null测试传入Pojo类 先建立一个User类,比较关键的地方在于,需要编写对应的getter方法publicclassUser{privateLongid;privateStringusername;privateStringpassword;privateCharactergender;privateIntegerage;publicUser(){}publicUser(Stringusername,Stringpassword,Charactergender,Integerage){this。usernameusername;this。passwordpassword;this。gendergender;this。ageage;}publicLonggetId(){returnid;}publicvoidsetId(Longid){this。idid;}publicStringgetUsername(){returnusername;}publicvoidsetUsername(Stringusername){this。usernameusername;}publicStringgetPassword(){returnpassword;}publicvoidsetPassword(Stringpassword){this。passwordpassword;}publicCharactergetGender(){returngender;}publicvoidsetGender(Charactergender){this。gendergender;}publicIntegergetAge(){returnage;}publicvoidsetAge(Integerage){this。ageage;}}复制代码 Usermapper。xml的内容不需要修改publicclassMain{publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserusernewUser(Susan,qweasd,1,29);intcountsqlSession。insert(user。insertUser,user);if(count1){System。out。println(插入成功);}else{System。out。println(insertfailed);}sqlSession。close();}}复制代码 执行后依然可以看到插入成功 使用Pojo类和Map最大的区别点在于,必须传入存在的getter方法名,举个例子,Mybatis会把getUsername的get去掉,首字母U改成小写的u,最终占位符传入的就是username,如果不存在getter则会报错,而不是传入null 在Mapper的编写中,有一个parameterType参数类型可以传入全限定类名,但一般这个可以忽略不写,如果要写格式如下 动态删除 在UserMapper。xml中,添加一个delete标签,由于传参是个简单数据类型,且只有一个参数,占位符的key可以随便写mappernamespaceuserdeleteiddeleteUserdeletefromtuserwhereid{suibiannixie}deletemapper复制代码 调用sqlSession的delete方法,传入idpublicclassMain{publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionsqlSessionSqlSessionUtil。openSqlSession();intcountsqlSession。delete(user。deleteUser,8);if(count1){System。out。println(删除成功);}else{System。out。println(deletefailed);}sqlSession。close();}}复制代码 可以看到8被成功替换了{suibiannixie} 动态修改 这里演示只根据用户id修改用户名mappernamespaceuserupdateidupdateUserupdatetusersetusername{username}whereid{id}updatemapper复制代码 调用sqlSession的update方法,传入User对象publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserusernewUser();user。setUsername(tester);user。setId(14L);intcountsqlSession。delete(user。updateUser,user);if(count1){System。out。println(更新用户名成功);}else{System。out。println(updatefailed);}sqlSession。close();}复制代码 更新成功如下 使用Mybatis查询数据 与增删改有点区别,他们都是返回数据表中被影响的数据行数,而查询则会返回一个封装好的查询结果集,比如查询用户表,那么返回结果就是单个用户对象,或者用户对象的集合 编写mapper,图方便用mappernamespaceuserselectidselectByIdselectfromtuserwhereid{id}selectmapper复制代码 执行sqlSession的selectOne方法publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionsqlSessionSqlSessionUtil。openSqlSession();ObjectusersqlSession。selectOne(user。selectById,1);System。out。println(user);sqlSession。close();}复制代码 执行程序后可以看到如下报错 意思就是mybatis检测到你没有为select标签提供对应的结果映射类型,不提供的话它没有办法知道如何处理对应的结果集,再次修改mapper,添加一个resultType标签,填写全限定类名mappernamespaceuserselectidselectByIdresultTypecn。mgl。pojo。Userselectfromtuserwhereid{id}selectmapper复制代码 再次执行便能查到对应用户信息,这里需要注意一下几点当用户表字段用Pojo类命名格式不一致 为了演示效果,为用户表额外添加一个underScoreCase(下划线命名)的字段,并给对应数据添加日期类型的值 Pojo类中添加对应的字段和getter方法,java命名规范是小驼峰 此时再次执行查询程序,可以看到对象映射并没有成功,对应的createdAt字段为null 原因就是因为属性名没有对上,有以下几种解决方案通过sql编写别名,让查询结果字段名对的上createdatascreatedAtselectidselectByIdresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserwhereid{id}select复制代码通过mybatis提供的设置选项,开启驼峰命名自动映射,前提是转换后的名字能对得上,如数据库的字段为ab,转化后就成了aBsettingssettingnamemapUnderscoreToCamelCasevaluetruesettingsettingnamelogImplvalueSTDOUTLOGGINGsettingsettings复制代码通过select标签的resultMap映射(后面会说) 前两种方式任选其一配置后,再次执行查询语句,可以看到createdAt字段已经有值了 查询多条数据 sqlSession查多条就需要调用selectList方法,虽然返回的是List集合,但resultType仍然指定实体类名,新增一个select标签selectidselectAllresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserselect复制代码 调用selectList方法,接受类型为User集合publicstaticvoidmain(String〔〕args)throwsIOException{SqlSessionsqlSessionSqlSessionUtil。openSqlSession();ListUseruserssqlSession。selectList(user。selectAll);users。forEach(System。out::println);sqlSession。close();}复制代码 查询成功 实际上,查询结果为单条的情况下也可以使用List进行接收使用接口代理形式 上述的使用方式有一个非常大的问题在于,开发者需要记住且拼接对应的映射id,这种方式并不便于开发维护,所以Mybatis还提供了一个更为方便使用的代理模式 首先新建一个接口,这里命名为UserDaopublicinterfaceUserDao{intinsertUser(Useruser);intdeleteUser(Longid);intupdateUser(Usersuer);UserselectById(Longid);ListUserselectAll();}复制代码 接着配置对应的mapper,这里需要注意的是,namespace不能再乱写了,需要写成UserDao的全限定类名,并且所有sql标签的id属性也要和接口中的方法名保持一致,如果不一致,则会抛出对应的异常mappernamespacecn。mgl。dao。UserDaoinsertidinsertUserparameterTypecn。mgl。pojo。Userinsertintotuser(username,password,age,gender)values({username},{password},{age},{gender})insertdeleteiddeleteUserdeletefromtuserwhereid{suibiannixie}deleteupdateidupdateUserupdatetusersetusername{username}whereid{id}updateselectidselectByIdresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserwhereid{id}selectselectidselectAllresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserselectmapper复制代码 调用执行的时候不再使用namespace拼接id的方式,而是使用sqlSession的getMapper方法,传入对应的Mapper接口的类对象,mybatis底层实现会去进行解析反射等一系列操作,最终生成一个代理对象,开发者只需要调用接口中的方法即可完成数据库操作,实现的效果完全等价的publicstaticvoidmain(String〔〕args){SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);ListUserusersmapper。selectAll();users。forEach(System。out::println);sqlSession。close();}复制代码 这样的方式更为常用关于{}和{} 学过JDBC开发的都知道,有个PreparedStatement和Statement对象{}底层实现对应着PreparedStatement,有防SQL注入的作用,先编译再进行占位符替换,上面所有的示例中都是使用这种方式,用的最多{}底层对应着Statement,有SQL注入的风险,一般在SQL语句需要对保留关键字进行拼接的情况下使用,能不用就不用举几个需要使用{}的情况查询结果集需要根据username排序,排序方式需要自定义传入,编写如下SQLselectidselectAllOfSortresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserorderbyusername{sort}select复制代码 这种情况下才适合使用,引用如果使用{},会将传入的asc或者desc加上字符的引号,那么对应的语句便不合法,因为关键字是不需要加引号的表名不能写死的情况下,需要动态修改执行,可以使用,原理还是引号导致的语法问题SQL语句使用了in(????),如deletefromxxxwhereidin({keys})此时也可以使用,原理同上模糊查询的情况下,如selectfromxxxwhereusernamelike{key} 模糊查询下如果不想使用{},可以选择{},但是需要一定拼接技巧使用concat函数,语句可以这样写selectfromxxxwhereusernamelikeconcat(,{key},),最终出来的效果就是合法的语句使用双引号包括,selectfromxxxwhereusernamelike{key}typeAliases类型别名 主要用于给数据类型起别名,在mybatisconfig。xml中进行配置,需要注意顺序,在后续的mapper文件中,指定类型时就不再需要写冗长的全限定类名,在使用的时候是不区分大小写的typeAliasestypeAliastypecn。mgl。pojo。UseraliasUsertypeAliases复制代码 像这个查询语句的resultType,原本写的全限定类名就可以替换成简短的别名selectidselectAllresultTypeUserselectid,username,password,gender,age,createdatascreatedAtfromtuserselect复制代码 此时如果一个类名配置一次显然也会麻烦,恰好提供了一个package标签,只需要指定包名,那么这个包下的所有类都会生成一个对应的简类名别名供使用typeAliasespackagenamecn。mgl。pojotypeAliases复制代码Mybatis配置文件配置项理解 其实这一环在官网的文档中有说明,遇到具体的配置问题可以随时查询mybatis。orgmybatis3z 下面只简单的讲解properties 提供了更灵活的配置使用,像上述demo中就通过资源文件动态配置了JDBC信息数据,可以使用resource或者url属性进行文件配置导入,也可使用子标签配置属性前提是资源文件在类路径下才能找到资源environments 用于配置环境信息,有一个default属性,可以用于执行采用那个environment标签的配置信息,需要和对应标签的id一致environment,这里配置的是具体的数据源信息,其中的id属性就是给其父标签environments的default属性使用的一个environment可以理解为就是一个环境,对应着一个SqlSessionFactory对象transactionManager 用于配置事务管理器,其中有个type属性可供使用JDBC,配置为这个值,则采用JDBC的事务机制,相当于默认开启事务并且不会自动提交,需要开发者手动的进行commit事务MANAGED,配置这个值会将事务机制交给其他容器管理,如果没有对应容器则没有事务,执行一次DML(增删改查)就提交一次dataSource 用于指定数据源相关的信息,此标签有一个type属性可以配置连接池的策略,不同的type决定着子标签的property可以配置不同的属性值UNPOOLED采用最传统的方式获取数据库链接,并且不会有连接池的概念,每一次访问都是新的链接driver配置jdbc驱动的java全限定类名url配置数据库的jdbc地址username配置数据库用户名password配置数据库用户密码defaultTransactionlsolationLevel配置默认的事务隔离级别(此处为Mysql的知识点)defaultNetworkTimeout配置等待数据库操作的默认网络超时时间POOLED采用了连接池的规范实现上述UNPOOLED策略列出的所有都可以配置poolMaximumActiveConnections在任意时间可以使用的最大连接数量,默认值是10,若一个请求占用一个线程,此时有20个请求,同一时间内只能处理10个请求,线程就会占满,第11个请求只能等待空闲线程poolMaximumldleConnections任意时间可能存在的空闲连接数,默认值是5,如果已经有个5个线程是空闲状态,当出现第6个空闲线程时,会将此线程关闭减少数据库开销poolMaximumCheckoutTime强制回归线程池时间,默认值为20spoolTimetoWait当无法获取到空闲连接时,每20s打印一次日志其他不太常用property 该标签提供了更为灵活的配置方式,如将配置文件用其他文件格式的资源文件进行管理,上面第一个例子就是使用了引入外部资源的方式,配置resource属性mapper 用于指定SQL映射文件路径通过引入类路径下的资源文件mappersmapperresourceUserMapper。xmlmappers复制代码通过file:格式的url进行查找,一般不会使用这个,移植性太差通过Class接口的全限定类型进行配置,mybatis底层会进行动态代理,假设有这么一个UserMapper类,配置完后会自动进行映射,前提是对应的namespace和id等需要与类名及方法名一一对应上,还有最关键一点:xml文件需要和接口类放到同一个目录下mappersmapperclasscn。mgl。mapper。UserMappermappers复制代码通过配置包名,这个是基于方式3的前提下,更便捷的配置映射,原本使用的mapper标签更改为package标签,整个包下的接口都会批量的进行映射mapperspackagenamecn。mgl。mappermappers复制代码传入数据后获取到自增的id 正常情况下,执行一个新增方法,会传入一个对应的实体类,但一般这个实体类是没有id的,如果需要插入成功后数据表中对应的自增id,要么自己根据条件再查询一遍获取,或者可以通过配置,在insert标签中开启useGeneratedKeys和指定propertKeyinsertidinsertUserparameterTypecn。mgl。pojo。UseruseGeneratedKeystruekeyPropertyidinsertintotuser(username,password,age,gender)values({username},{password},{age},{gender})insert复制代码 后续的使用中,没有添加上述属性时,传入的User实例是不会有id属性的,配置完了再次执行查看输出publicstaticvoidmain(String〔〕args){SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);UserusernewUser(ma,!23,1,20);mapper。insertUser(user);System。out。println(user);sqlSession。close();}复制代码 可以看到id成功被赋值了 Mybatis的参数处理 使用的代码示例当中,语句标签的parameterType上面说过可以省略,sql中使用的{key}实际是个省略写法,他的完整写法如下。。。sql忽略。。。wherename{name,javaTypeString,jdbcTypeVARCHAR}复制代码 一般情况是都忽略不用写的,像7种基本数据类型以及对应的包装类,还有String,Date这些参数类型,mybatis会自己进行类型推导,最终由推导结果来觉得调用JDBC中的setString,setInt等等多参数处理 之前演示的都是单参数形式,多参数的处理有一些规则需要遵守,先编写一个需要传入两个参数的sql及方法selectidgiveTwoParameterresultTypecn。mgl。pojo。Userselectid,username,password,gender,age,createdatascreatedAtfromtuserwhereusername{usernmae}andage{age}select复制代码 新增一个接口方法UsergiveTwoParameter(Stringusername,Integerage);复制代码 执行方法,传入一个姓名和年龄publicstaticvoidmain(String〔〕args){SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);mapper。giveTwoParameter(ma,20);sqlSession。close();}复制代码 不出意外的话可以看到报错,提示username找不到,可以用的占位符key有〔arg1,arg0,param1,param2〕,需要说明一下,如果使用arg则后面的序号从0开始,如果使用param,则后面的序号从1开始 只需要按照提示,将原本的{username}和{age}替换成arg格式或param格式的即可 实际上,mybatis在底层实现中,有一个Map集合进行数据收集MapString,ObjectmapnewHashMap();map。put(arg0,key);map。put(param1,key);每个参数都以此类推复制代码Param注解的使用 MyBatis在代理生成对象时,会去检查入参的参数是否带有Parmas的注解,如果有,则会新建一个Map集合用来存放最终的keyandvalue,当使用了这个注解后,等价于把默认的arg格式的key给替换成我们自定义的key,但param格式的key还保留着。修改接口中的参数,添加对应的注解值UsergiveTwoParameter(Param(username)Stringusername,Param(age)Integerage);复制代码 对应的UserMapper。xml中占位符使用注解传入的key即可可以尝试用Map去接受返回值 当不知道用什么对应的实体类接收SQL的返回值时,可以用Map类型,Mybatis会自动转换成键值对的形式,但一般不建议这么做,可读性太差了当数据库字段与Java对象属性无法匹配时可以通过sql语句的as关键字对需要查询出数据的列名进行重命名成Java属性对应的name上面演示过的通过配置setting标签中的,驼峰转换自动映射,前提是数据库的列名和Java的属性名都遵循规范,如字段名为username,则对应的Java属性名为userNamesettingssettingnamemapUnderscoreToCamelCasevaluetruesettingsettings复制代码通过定义resultMap来手动映射,以示例中出现过的的User类举例resultMapidcustomMaptypecn。mgl。pojo。UseridpropertyidcolumnididresultpropertyagecolumnageresultresultpropertyusernamecolumnusernameresultresultpropertypasswordcolumnpasswordresultresultpropertygendercolumngenderresultresultpropertyagecolumnageresultresultpropertycreatedAtcolumncreatedatresultresultMapselectidgetByXxxresultMapcustomMapselect复制代码 这里的id为配置主键,其他的属性用result标签,其中property为Java对象的属性,column为查询结果集的字段名,后续在指定select标签的返回值时,不再指定resultType,而是指定resultMap,传入的就是定义map时的idMybatis内置的类型 像一些常规的int、long,map,string等类型,不需要定义就可以在resultType中使用,这是因为框架内置了映射,具体的内置配置可以查看mybatis。orgmybatis3z,翻到类型别名处,文档又说明Mybatis比较高级的特性动态SQL 所谓的动态,就是能方便开发者进行一些有条件及便携的sql语句拼接if标签 以一个常见的场景来说,需要查询某个数据列表,可以提供一些查询条件,这些条件可传可不传,此时就可以使用if标签了selectidtestIfLabelparameterTypeuserselectfromtuserwhere11iftestgender!nullandgender{gender}ifselect复制代码 这里where语句后面的11是为了保证即使if标签的条件没有通过,也不会报语句错误。当传入的User对象的gender对象不为空,则会进行拼接,否则不会,执行到11就结束了where标签 在上面的例子中,为了不报错需要书写11这种特殊处理,where标签能够更智能的处理这种情况在where标签下的if标签,如果条件不通过,则不会生成子句会自动去掉某些条件前多余的and或者or,参考上面if标签的使用中andgender{gender}中的and 利用where标签改造一下selectidtestWhereLabelparameterTypeuserselectfromtuserwhereiftestgender!nullandgender{gender}ifwhereselect复制代码 此时若过gender为null,最终的sql为selectfromtuser,反之则是selectfromtuserwheregender{gender},这里的and会被智能的去掉,如果有多个子标签,则and会被保留trim标签 个人用的不太多,主要作用是可以在语句前后做增加和删除prefix:添加前缀suffix:添加后缀prefixOverrides:删除前缀suffixOverrides:删除后缀 简单的演示下使用selectidtestTrimLabelparameterTypeuserselectfromtusertrimprefixwheresuffixOverrides,iftestgender!nullgender{gender},iftrimselect复制代码 当if标签中的条件不满足时,也会智能不处理,若满足条件,则在包括的语句前添加prefix,并且删除掉语句末尾的suffixOverridesset标签 一般用于update语句中,用起来其实和where标签很类似updateidtestSetLabelparameterTypecn。mgl。pojo。Userupdatetusersetiftestusername!nullandusername!username{username},ififtestgender!nullandgender!gender{gender},ifsetupdate复制代码 上面的语句中,实现的需求就是当username和gender不是null或者空字符串时,才会拼接set语句,并且会去掉子句后面的,。这里有个问题就是当二者都不满足条件,就会产出一条错误的sql语句,更推荐用if手写sql的方式来保证sql语句的正确性choesewhenotherwise 这个其实就相当于if。。。elseif。。。else的语法,都是配合着使用,还是以查询为例子,可以提供username,age的查询条件,当提供了username则使用username,如果没有提供username却提供了age,则使用age,否则则使用默认的查询条件selectidtestChooseLabelparameterTypecn。mgl。pojo。Userselectfromtuserwherechoosewhentestusername!nullandusername!username{username}whenwhentestgender!nullandgender!gender{gender}whenotherwiseage10otherwisechoosewhereselect复制代码foreach标签 可用于循环遍历数组或集合,一般常用与批量增删改操作,有以下属性可以使用collection:需要被循环的值item:被循环的每个元素可用别名index:循环的索引open:在子句生成的开头插入指定字符close:在子句生成的结尾插入指定字符separator:循环之间的分隔符 以删除语句为例子,这里提供了两种写法,都可以实现批量删除deleteidtestForeachOfDeldeletefromtuserwhereforeachcollectionidsitemidseparator,id{id}foreachdeletedeleteidtestForeachOfDel2deletefromtuserwhereidin(foreachcollectionidsitemidseparator,{id}foreach)delete复制代码 需要注意的是,这里collection使用了ids,是因为对应接口定义时参数使用了Param(ids)Listids,否则运行后会报错,并告知你应该使用arg0,collection,list这些底层映射的默认可用值,显然这些可读性不好,更应该指定map的key,目前的设置,可用的就变为了ids和param1 像批量插入和修改都是同理的,只要是合法的sql语句就能利用foreach标签批量构建出预期值sql标签和include标签 前者用来定义sql片段,后者用来引入到某个sql中,用批量删除的语句来演示deleteidtestForeachOfDel2includerefiddelSliceincludewhereidin(foreachcollectionidsitemidseparator,{id}foreach)deletesqliddelSlicedeletefromtusersql复制代码 用法比较简单,可以根据需求来决定提取哪些是可以复用的语句,比如繁琐的字段名就很适合定义为片段Mybatis的高级映射 假设当前新增了一个Dept类存储部门信息,且每个用户都有对应的部门,此时就形成了多对一的关系,那么User对象当中就应该多一个Dept类型的成员属性 先定义一个部门类packagecn。mgl。pojo;publicclassDept{privateLongdeptid;privateStringdeptname;publicLonggetDeptid(){returndeptid;}publicvoidsetDeptid(Longdeptid){this。deptiddeptid;}publicStringgetDeptname(){returndeptname;}publicvoidsetDeptname(Stringdeptname){this。deptnamedeptname;}}复制代码 在数据库中也要创建对应的表结构CREATETABLEtdept(deptidintDEFAULTNULL,deptnamevarchar(20)DEFAULTNULL)ENGINEInnoDBDEFAULTCHARSETutf8mb4COLLATEutf8mb40900aici复制代码 除此之外还需要对原本User类添加一个成员属性dept,类型为Dept,并添加对应的getter,setter函数 (相关的数据库数据自行添加)多对一映射的方式:一条外链接Sql映射结果resultMapiduserMaptypeuseridpropertyidcolumnididresultpropertyusernamecolumnusernameresultresultpropertydept。deptidcolumndeptidresultresultpropertydept。deptnamecolumndeptnameresultresultMapselectidtestMappingresultMapuserMapselecta。username,a。age,a。gender,a。createdat,a。updatedat,a。id,b。deptid,b。deptnamefromtuseraleftjointdeptbona。deptidb。deptidselect复制代码 这种用法的select标签不使用resultType而是resultMap,注意对应的写法:中的dept为User类中的属性名,不可以随便写,这里可以不需要把所有映射都写上,如果确定类属性和结果集列名匹配得上可以不写 执行相关语句后,查询用户时也能把部门信息查出来并映射到成员嵌套对象中 使用映射中的关联标签resultMapiduserMaptypeuseridpropertyidcolumnididresultpropertyusernamecolumnusernameresultidpropertydeptIdcolumndeptididresultpropertydeptNamecolumndeptnameresultassociation!resultpropertydept。deptidcolumndeptidresult!resultpropertydept。deptnamecolumndeptnameresultresultMap复制代码 只需要把原本的手动级联映射改为association标签,设置javaType为Dept类,接着配置类属性和字段名映射关系,实现效果和方式一完全一致分步查询 所谓的分布查询就是将查询分为步骤一、步骤二。。。以此类推,比如这个实例中,可以先查询出用户表信息,这是第一步,由于用户表存储了部门id,所以可以通过查询出来的部门id再去查询相关部门信息,这是第二步 新增一个DeptDao的接口,目前只写一个抽象方法packagecn。mgl。dao;importcn。mgl。pojo。Dept;publicinterfaceDeptDao{DeptgetById(Longid);}复制代码 接着添加对应的deptMapper。xml,编写一条根据id查询信息的sql(需要在mybatisconfig。xml添加mapper)lt;?xmlversion1。0encodingUTF8?!DOCTYPEmapperPUBLICmybatis。orgDTDMapper3。0ENhttp:mybatis。orgdtdmybatis3mapper。dtdmappernamespacecn。mgl。dao。DeptDaoselectidgetByIdresultTypedeptselectdeptid,deptnamefromtdeptwheredeptid{deptId}selectmapper复制代码 改写原本UserMapper。xml编写的查询,不需要外链接,只写简单的单表查询,并且association中添加一个select属性,指定为namespaceid的格式,column属性则为子语句的查询条件resultMapiduserMaptypeuseridpropertyidcolumnididresultpropertyusernamecolumnusernameresultassociation!!idpropertydeptIdcolumndeptidid!resultpropertydeptNamecolumndeptnameresult!association!resultpropertydept。deptidcolumndeptidresult!resultpropertydept。deptnamecolumndeptnameresultresultMapselectidtestMappingresultMapuserMapselectusername,age,gender,createdat,updatedat,idfromtuserselect复制代码 执行查询后,可以看到执行了两条语句,并且会自动将第一步查询出来的deptid当作查询参数传入第二条sql中 这样做的好处:代码的复用性增强,耦合度降低支持延迟加载,访问不到的数据可以先不查询,增加查询效率看完多对一,接下来看看一对多利用collection标签 其实区别只是主体不同,现在要改成查询部门下的全部用户,则主体变更为部门,那么此时部门的类中应该有一个用户类型的集合才存储该部门下的所有用户(下面省略Java代码编写,自行操作)resultMapidclazzCollecttypeclazzidpropertyclazzIdcolumndeptididresultpropertyclazzNamecolumndeptnameresultcollectionpropertycollectionofTypeuseridpropertyidcolumnididresultpropertyusernamecolumnusernameresultcollectionresultMapselectidtestGetByCollectionresultMapclazzCollectselecta。deptid,a。deptname,b。id,b。deptid,b。username,b。gender,b。agefromtdeptaleftjointuserbona。deptidb。deptidselect复制代码 在resultMap中,有一个collection子标签,其中的property写了collection是因为在User类中有成员属性名就是collection,后面的ofType填写这个集合中类型,子标签的用法就大同小异了 执行查询后,虽然sql结果集为4条,但会将最终映射出来的数据只有3条(同一个部门归类),且Dept类中的集合成功赋值 和一对多类似的分步查询 这个原理就一样了,先做第一步查出部门,再做第二步查出部门下所有用户resultMapidclazzCollecttypeclazzidpropertyclazzIdcolumndeptididresultpropertyclazzNamecolumndeptnameresultcollectionpropertycollectionselectcn。mgl。dao。UserDao。selectByDeptIdcolumndeptidcollectionresultMapselectidtestGetByCollectionresultMapclazzCollectselectdeptid,deptnamefromtdeptselect复制代码 与第一种方式的区别是,原本的连表查询改为单表查询,并且collection标签中新增select属性,对应的查询方法自行添加,实现的最终效果也完全一致延迟加载 所谓的延迟加载就是没用到的时候不执行对应的sql,用到再执行,以减少查询的方式增加性能 一对多与多对一中的延迟加载机制是一样的对某个查询单独使用fetchTypelazy,用上面的例子演示resultMapidclazzCollecttypeclazzidpropertyclazzIdcolumndeptididresultpropertyclazzNamecolumndeptnameresultcollectionfetchTypelazypropertycollectionselectcn。mgl。dao。UserDao。selectByDeptIdcolumndeptidcollectionresultMapselectidtestGetByCollectionresultMapclazzCollectselectdeptid,deptnamefromtdeptselect复制代码在全局配置中,添加一个setting标签,设置lazyLoadingEnabledtrue,此时全部sql都会走延时加载settingssettingnamemapUnderscoreToCamelCasevaluetruesettingsettingnamelogImplvalueSTDOUTLOGGINGsettingsettingnamelazyLoadingEnabledvaluetruesettingsettings复制代码 若在开启全局配置后,就是想要某个sql不走延时加载,只需要添加fetchTypeeager即可 一般推荐全局开启,减少查询增加性能了解Mybatis的缓存机制 如这次执行查询后,将查询结果放到内存中,如果下次还是这条查询语句,则直接从缓存中取,不需要再去数据库中查询,以此来提高效率性能,但是只对select语句有效一级缓存 针对的事sqlSession 下面的例子中:SqlSessionUtil。openSqlSession简单封装的获取sqlSession对象mapper。test是一条简单的selectSqlmapper。update是一条简单的updateSqlSqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);ListUsertestmapper。test();ListUsertest2mapper。test();System。out。println(test2);System。out。println(test);复制代码 运行后,select只执行了一次,第二次再次查询直接从缓存中取,所以没有sql执行日志输出 下面演示的是一级缓存失效手动执行sqlSession的clearCache方法,sqlSession。clearCache();,比较简单不演示在同一个SqlSession的情况下,两条select语句之间执行了update,insert,delete任一,一级缓存会被清空,代码用update举例子SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);ListUsertestmapper。test();UserusernewUser();sqlSession。clearCache();user。setId(14L);mapper。updateUser(user);ListUsertest2mapper。test();System。out。println(test2);System。out。println(test);复制代码 运行后可以看到,即使再次执行相同的select,也不会走缓存 不同SqlSession执行相同的select查询SqlSessionsqlSessionSqlSessionUtil。openSqlSession();SqlSessionsqlSession2SqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);UserDaomapper2sqlSession2。getMapper(UserDao。class);ListUsertestmapper。test();ListUsertest2mapper2。test();System。out。println(test2);System。out。println(test);复制代码 运行后可以看到执行了两次查询 二级缓存 这个设置需要满足一定的条件才会开启,且是针对SqlSessionFactory的,前面说过可以根据不同的环境获取不同的SqlSessionFactory,如有两个SqlSession,即使他们都执行一样的sql语句,也不会走二级缓存 开启前提条件:mybatisconfig。xml中,settings下的标签卡其了全局缓存,默认就是true,,相当于不配置就是开启在需要使用缓存的SqlMapper。xml中添加一个标签被缓存的pojo类必须实现Serializable接口,让其拥有可序列化特性sqlSession对象关闭或提交之后,一级缓存中的数据才会被写入二级缓存中,此时缓存才能体现 前三步比较简单,直接看第四步的代码SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);ListUsertestmapper。test();System。out。println(test);sqlSession。close();SqlSessionsqlSession2SqlSessionUtil。openSqlSession();UserDaomapper2sqlSession2。getMapper(UserDao。class);ListUsertest2mapper2。test();System。out。println(test2);复制代码 运行后的输出,有一句CacheHitRatio且没有第二次sql日志输出,说明缓存命中了 二级缓存失效情况和一级缓存一样,中间执行过select,update,insert语句时,缓存也会失效不同的sqlSessionFactory对象 想测试的话比较简单,便不再演示查询分页Mybatis使用PageHelper插件 这个插件是帮助处理分页需求的,正常情况下分页查询需要接上limitstartIndex,pageSize进行查询,通过这个插件可以让开发过程减少一些重复冗余的分页编写 通过maven添加依赖dependencygroupIdcom。github。pagehelpergroupIdpagehelperartifactIdversion5。3。2versiondependency复制代码 接着在mybatisconfig。xml中添加插件配置,注意顺序问题pluginsplugininterceptorcom。github。pagehelper。PageInterceptorpluginplugins复制代码 sql中不需要添加分页语句,只需要在查询语句执行前,调用一次startPage方法并传入相关参数,简单讲讲传参的计算,假设以10条数据为一页,查询第2页的数据,则需要传入2,10,后续的计算中则为(21)10,10,最终结果就是limit10,10publicstaticvoidmain(String〔〕args){SqlSessionsqlSessionSqlSessionUtil。openSqlSession();UserDaomappersqlSession。getMapper(UserDao。class);PageHelper。startPage(2,10);ListUserusersmapper。selectAll();users。forEach(System。out::println);PageInfoUsertPageInfonewPageInfo(users);sqlSession。close();}复制代码 简单的说下原理,执行了插件方法startPage后,会缓存分页数据到ThreadLoacl当中,只会对执行了startPage方法后的第一个查询语句起效 如果不用分页插件也可以实现,先查询出表的总条数,再按照分页规则自行处理sql即可,插件只是帮忙处理了这几个步骤注解式开发 mybatis除了xml以外,还提供了注解式开发,不过这种方式并不推荐使用,因为不好维护,除非是非常简单的sql语句,能用xml尽量用,不过也可以了解下使用方式,以基础的增删改查为例子,分别使用对应的注解,并传入sql的字符串,效果与xml文件配置是相同的publicinterfaceUserAnnoDao{Insert(insertintotuser(username,password,age,gender)values({username},{password},{age},{gender}))intinsertUser(Useruser);Delete(deletefromtuserwhereid{id}})intdeleteUser(Longid);Update(updatetusersetusername{username}whereid{id})intupdateUser(Usersuer);Select(selectfromtuserwhereid{id}})UserselectById(Longid);}复制代码 完:)