mybatis 养吾剑总结
基本配置
创建mybatis的主配置文件,指定数据库环境和事务类型
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="jdbcTypeForNull" value="NULL"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<typeAliases>
<package name="com.jp.haiyou.attendance.web.vo"/>
</typeAliases>
<!--environments是所有环境标签,子标签是单个的数据库环境。可以指定一个默认采用的数据库-->
<environments default="mysql">
<!--这里配置的是单个的数据库环境,这里是msql-->
<environment id="mysql">
<!--配置事务类型,这里采用jdbc-->
<transactionManager type="JDBC"></transactionManager>
<!--配置采用的连接池,这里使用内置的pooled-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="JIANGkui1"/>
</dataSource>
</environment>
</environments>
<!--指定配置文件的位置,mappers是指一个dao接口所对应的具体实现方式,其存放位置必须在resources的同名包下-->
<mappers>
<mapper resource="dao/IAccountDao.xml"/>
</mappers>
</configuration>
如果不想将接口和mapper文件分别放到src/main/java和src/main/resources中,而是全部放到src/main/java,那么在构建的时候需要指定maven打包需要包括xml文件,具体配置如下:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
创建和dao接口相匹配的映射配置文件
<mapper namespace="dao.IAccountDao">
<!--这里的dao里的id是方法名,如果方法有返回值,需要封装的话,需要通过resultType指定返回值类型-->
<select id="findAll" resultType="domain.Account">
select * from account
</select>
</mapper>
sql语句简单的话,也可以使用注解的方式来配置sql语句
@Select("select * from account")
Account findAccountByname(String name);
不使用spring整合的话,需要自己配置SqlSessionFactory :
// 通过mybatis提供的resources工具类得到配置文件的流,然后使用工厂构建器构建使用该配置文件构建出的工厂
InputStream in = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 工厂生产session对象,这里我们主要使用的对象,该对象可以生产代理dao,并且一个session是一个事务
SqlSession sqlSession = factory.openSession();
// 使用getmapper方法获得代理dao
IAccountDao accountDao = sqlSession.getMapper(IAccountDao.class);
List<Account> all = accountDao.findAll();
for (Account account : all) {
System.out.println(account);
}
// 释放资源:由于流并不是給包装类使用的,这里注意要关闭流。
// sqlSession.commit();
sqlSession.close();
in.close();
基本增删改查
<select id="findAll" resultType="domain.Account">
select * from account
</select>
<!--占位符使用el表达式方式#{name},el表达式的作用域方法传入的参数parameterType,属性名是get方法.注意ddl都需要commit-->
<!--selectKey三个属性分别表示属性名称,返回值类型,操作顺序,就是把select的结果注入到传入对象的id属性中去-->
<!--非自增随机赋予uuid型逐渐也可以获取,order="befor" 返回值string 语句写select replace(uuid(), '-', '')即可-->
<insert id="saveAccount" parameterType="domain.Account">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into account(name,money) values(#{name},#{money})
</insert>
<update id="updateAccount" parameterType="domain.Account">
update account set name=#{name},money=#{money} where id=#{id}
</update>
<!--参数只有一个的情况下,占位符叫id或uid,可以随便取,它将按照类型获取值,类似autowire-->
<delete id="deleteAccount" parameterType="int">
delete from account where id=#{id}
</delete>
<select id="findAccountById" parameterType="int" resultType="domain.Account">
select * from account where id=#{id}
</select>
<!--模糊查询时,可以写${value}的形式,这样可以拼接其他字符串,如%${value}%,这种形式中value的值是固定的,只能写value-->
<select id="findAccountByName" parameterType="string" resultType="domain.Account">
select * from account where name like #{name}
</select>
<select id="findTotal" resultType="int">
select count(id) from account
</select>
传入参数和返回值封装
【parameterType和resultType】
1、mybatis使用ognl(对象图导航语言)表达式解析对象的值,#/$括号中的域对象即为pojo对象。后者拼写时不会带‘’符号,所以要小写sql注入攻击。
2、显然,如果要使用跨关系查询时,只需要把多个对象组成一个查询vo对象或者map,然后在sql中调用即可。需要注意没有传值时的处理方式。
3、parameterType只是用来指定ognl表达式的域对象的,不写它时,可以用指定对象取值的方法来获取参数,如#{user.id}。多参数时,可以位置索引如#{0}来指定参数使用。
4、方法中直接使用注解定义参数别名,如@Param(“id”),xml可以直接使用#{id}。
【返回值封装】
数据库列名如果和entity类set方法名一致,可以自动封装,如果不一样,可以采用如下方法。
1、查询语句起别名。
2、使用resultMap 标签描述列名和实体类的对应关系。然后在select标签中把resultType换成resultMap即可。注意,以后所有使用到resultMap的地方也是一样的,所有实体类与数据列名一致的地方都可以省略。
<resultMap id="accountMpa" type="domain.Account">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="money" column="money"/>
</resultMap>
动态查询,多表查询
<!--if test语句内可以不写#{}取得属性,但需要严格注意大小写,如果test为真,if内语句就会拼接到上面的select语句执行。可以使用!= == and or 等运算符。where子标签可以用于拼接where语句,可以把他看成自动写了一个where 1=1-->
<select id="findAccountByCondition" parameterType="domain.Account" resultType="domain.Account">
select * from account
<where>
<if test="id != null">
and id =#{id}
</if>
<if test="name != null">
and name like #{name}
</if>
<if test="money != null">
and money = #{money }
</if>
</where>
</select>
<!--for each标签,用来遍历集合拼接查询字符串-->
<select id="findAccountByIdInIds" parameterType="queryVo" resultType="domain.Account">
select * from account
<where>
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</where>
</select>
多对一(一对一)
<mapper namespace="dao.IAccountDao">
<resultMap id="accountUserMap" type="domain.Account">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="money" column="money"/>
<!--一对一的关系映射 property指定封装到内部哪一个对象,javatype指定类名-->
<association property="user" javaType="domain.User" >
<id property="id" column="user_id"/>
<result property="userName" column="userName"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
<select id="findAll" resultMap="accountUserMap">
select a.*,u.id user_id,u.address,u.birthday,u.sex,u.username from account a LEFT JOIN user u on a.userid=u.id
</select>
一对多
<mapper namespace="dao.IUserDao">
<resultMap id="userAccountMap" type="domain.User">
<id property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="domain.Account">
<id property="id" column="aid"/>
<result property="name" column="name"/>
<result property="money" column="money"/>
</collection>
</resultMap>
<select id="findAll" resultMap="userAccountMap">
select u.*,a.id aid,a.name,a.money from user u LEFT JOIN account a on u.id =a.userid
</select>
多对多
<resultMap id="roleMap" type="domain.Role">
<id property="roleId" column="roleId"/>
<result property="roleName" column="roleName"/>
<result property="roleDesc" column="roleDesc"/>
<collection property="users" ofType="domain.User">
<id property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
select r.*,u.* from `user` u
RIGHT JOIN user_role ur on u.id=ur.uid
RIGHT JOIN role r on r.roleId=ur.rid
</select>
如果一个对象中需要封装多个集合,resultMap中需要定义多个集合,列名不能重复。sql语句需要多表联合,将所有对象都查出来。如下是User中封装了account和role对象的sql语句:
<select id="findAll" resultMap="userAccountMap">
select u.*,r.*,a.id aid ,a.name,a.money from `user` u
LEFT JOIN user_role ur on u.id=ur.uid
LEFT JOIN role r on r.roleId=ur.rid
LEFT JOIN account a on a.userid=u.id
ORDER BY u.id;
</select>
【延迟加载】
<!--使用到一对一的关联对象时,自动根据select的语句发关联查询,在查询时一起发送1+n条sql语句。如果不配置懒加载,并不会延迟-->
<!--参数,column指外键列的列名,select是byid的语句,相当于select * from user where id=#{userid}。userid可以完全不在resultMap中体现。-->
<resultMap id="account_user_lazy" type="account">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="money" column="money"/>
<association property="user" javaType="user" column="userid" select="dao.IUserDao.findById"/>
</resultMap>
主配置文件:
<settings>
<!--指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
注意:懒加载时,调用tostring方法和getUser方法,都会触发sql语句发送。其他单独get属性时,并不会发送sql语句。
注意:懒加载时,调用tostring方法和getUser方法,都会触发sql语句发送。其他单独get属性时,并不会发送sql语句。
一对多查询时,写法也基本一样,查询user时,通过select属性指定一个accountdao的list findbyuserid(int uid)方法即可。传入的id值通过column参数指定本查询的id。相当于每次遍历一个User时,该配置会自动帮你调用一次findbyuserid方法。
缓存
1、sqlsession对象会默认缓存所有查询的结果集,这就是一级缓存。也就是说,在一个session中重复查询是没有意义的。但是,调用sqlsession.clearCache()可以清空缓存,清空后需要重新通过getMapper方法获取代理dao。
2、当调用sqlsession的修改,添加,删除,commit,close方法后,会自动清空一级缓存。
3、二级缓存是sqlsessionfactory级别的缓存,由一个工厂生产出的多个session共享同一个二级缓存。它存储的是数据,而非对象。它并不会发sql语句,但会将存储的数据新new一个对象交给调用方法,因此新建的对象内存地址是不同的。
4、二级缓存使用步骤:
1、在主配置文件中开启二级缓存。cacheEnabled=true
2、在映射文件的mapper标签中写。
3、在select标签中写userCache=“true”。
注解开发
1、单表情况下:删增改查的语句写法和xml一致。单参数情况下默认传入该参数作为作用域(基本类型直接按类型匹配),因此可以用#{属性名}的写法。多参数情况下,用@param注解修饰参数起别名,或者使用#{0}位置调用。
2、配置实体类映射的注解:
@Results(id = "accountMap",
value = {
@Result(id = true,column = "id",property = "id"),
@Result(property="name", column="name"),
@Result(property="money", column="money"),
@Result(property="userid", column="userid"),
})
该注解标在方法上,方法返回值按该映射封装的同时,可以指定一个id名称,給其他方法进行引用。其他方法使用@ResultMap(“accountMap”)注解即可引用,而不用重新写对应关系。
3、多表懒加载
@Select("select * from account")
@Results(
value = {
@Result(id = true,column = "id",property = "id"),
@Result(property="name", column="name"),
@Result(property="money", column="money"),
@Result(property="userid", column="userid"),
@Result(property = "user",column = "userid",
one = @One(select = "dao.IUserDao.findById",fetchType = FetchType.LAZY))
})
一对多情况下使用:
many = @Many(fetchType = FetchType.LAZY,select = "dao.IAccountDao.findAccountByUserId")
4、开启二级缓存,主配置文件开启后,在dao类名上使用@CacheNamespace(blocking=true)即可。
5、注解不支持逻辑判断和join查询,所以,复杂查询请尽量使用xml配置文件。
spring整合mybatis
<!--spring整合mybatis对象-->
<bean id="factoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="htyy.domain"/>
<!--所有settings的内容都可以写在这里-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="lazyLoadingEnabled" value="true"/>
<property name="aggressiveLazyLoading" value="false"/>
<setting name="jdbcTypeForNull" value="NULL"/>
</bean>
</property>
<!--引入原始的配置文件-->
<!--<property name="configLocation" value="mybatis.xml"/>-->
<!-- 当mybatis的xml文件和mapper接口不在相同包下时,需要用mapperLocations属性指定xml文件的路径。
*是个通配符,代表所有的文件,**代表所有目录下 -->
<!--<property name="mapperLocations" value="classpath:htyy/dao/**/*.xml"/>-->
</bean>
<!--4 自动扫描对象关系映射 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> -->
<!-- basePackage 属性是让你为映射器接口文件设置基本的包路径。 你可以使用分号或逗号 作为分隔符设置多于一个的包路径 -->
<property name="basePackage" value="htyy.dao"/>
</bean>
maven依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
番外篇
【分页插件pagehelper】
mybatis没有分页功能,我们可以使用该插件进行增强
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
两种注册方式:
1、使用mybatis配置文件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"></property>
</plugin>
</plugins>
2、使用spring整合
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
分页插件参数介绍
-
helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
特别注意:使用 SqlServer2012 数据库时,需要手动指定为 sqlserver2012,否则会使用 SqlServer2005 的方式进行分页。
你也可以实现 AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。 -
offsetAsPageNum:默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
-
rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
-
pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
-
reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
-
params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
-
supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。
-
autoRuntimeDialect:默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五。
-
closeConn:默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。
使用时,只需要在dao调用前执行startPage方法即可:
pageHelper.startPage(pageNum:1,pageSize:5)
List<User> users=UserDao.findAll();
//此时得到的users对象已经是分页后的,将它打包到pageinfo中即可。
PageInfo pageInfo=new PageInfo(users);
该pageInfo的一些属性如下:
1、size 当前页数量
2、total 总记录数
3、pages 总页数
4、list 结果集
5、prepage 前一页
6、nextpage
注意,pageinfo.list并不是list类型,而是一个page类型的lsit,因此print它并不会打印list信息。但遍历它可以获取存储的子条目
mybatis逆向工程插件
在不使用mybatis-plus的情况下,该插件可以很方便根据数据库表逆向生成entity和dao接口。
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml </configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="mysqlgenerator" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
<property name="enableSubPackages" value="false"/>
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test"
userId="root"
password="JIANGkui1" >
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="htyy.vo" targetProject="src/main/java" >
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口 targetPackage/targetProject:同javaModelGenerator
type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER -->
<sqlMapGenerator targetPackage="htyy.dao" targetProject="src/main/java" />
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="MIXEDMAPPER" targetPackage="htyy.dao" targetProject="src/main/java" />
<!-- 指定数据库表 -->
<table tableName="account"
enableCountByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
enableUpdateByExample="false">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
</context>
</generatorConfiguration>