文章目录
- 概述
- 比较
- 配置
- 写xml
- 加载上面配置并执行
- 加载配置的方法
- 方式一
- 执行方法
- 方式一
- 方式二(MyBatis映射器)
- 写配置文件的映射文件
- 设置对象的别名(简写)
- 获取自动生成的主键
- 查询结果和java的映射规则
- 基本类型映射:
- 简单对象映射:
- 嵌套对象映射:
- 忽略大小写:
- 下划线转驼峰:
- 构造器映射:
- 注解映射:
- 方式一
- 方式二
- 三个关键类的生命周期
- 1.1.SqlSessionFactoryBuilder
- 1.2.SqlSessionFactory
- 1.3.SqlSession
- 日志
- 日志等级
- 配置
- 打印入参
- #{}与${}比较
- 使用的场景
- 动态SQL
- if
- choose、when、otherwise
- trim、where、set
- foreach(批量操作)
- bind
- java API_MyBatis
- SqlSessionFactoryBuilder
- Resources 工具类
- SqlSessionFactory
- SqlSession
- SQL类
- 踩坑
概述
前生IBatis,现在很多包都是IBatis。
比较
MyBatis与JPA
MyBatis更加灵活,自己写sql,可以Sql优化,
JPA不行,因为自己生成SQL
配置
写xml
取名mybatis-config.xml,连接数据库
<?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"> 约束
<!--根元素,里面就是Mybatis配置-->
<configuration>
<!-- 环境配置:数据库的配置环境(怎么连接数据库) ,可以配置多个环境对象 environment,多个数据库的连接配置,可以通过 default="development" 来切换默认的环境
--><environments default="development"><environment id="development">
<!-- 事务管理器,type="JDBC"使用JDBC的事务管理 "JdbcTransaction" --><transactionManager type="JDBC"/>
<!-- 数据源配置 POOLED 连接池,对应 PooledDataSource--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///mybatis"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment><!-- <environment id="test">-->
<!-- ... -->
<!-- </environment>--></environments>
<!--映射配置,标注清楚sql语句写在哪里--><mappers><mapper resource="自己映射文件的目录,xml文件"/></mappers>
</configuration>
- 环境可以设置多个,连接不同的数据库,起不同的作用,可以将不同的用途设置给id,如测试,开发,生产环境等。
- 事务简称tx。
改进一下
数据库的连接配置可以提出来
写成一个jdbc.properties配置,配置加一个
<!-- 加载properties--><properties resource="jdbc.properties" /><transactionManager type="JDBC"/><dataSource type="POOLED"><!--通过k取value--><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment>
再改进一下 3.5.2开始之后可用
如果属性 ‘username’ 没有被配置,‘username’ 属性的值将为 ‘ut_user’
原理: ${tableName != null ? tableName : ‘global_constants’})
<dataSource type="POOLED"><property name="username" value="${username:ut_user}"/> </dataSource><properties resource="org/mybatis/example/config.properties"><!-- 开启上面的功能 ,可在value中自定义修改分隔符,写了就换--><property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>
加载上面配置并执行
会话工厂建造者建造会话工厂,会话工厂又生产会话,用会话去操作,
在会话执行的方法,通过命名空间+id获取。
执行的的各种方法都是在sqlSession类中
命名空间可以提出去,设置成常量
建造工厂可以提出去,创建一个utils
sqlSession用完要关掉,其他的不用
openSession()无参就需要手动提交 .commit(),如果传了true那么就自动提交,不用commit()
加载配置的方法
方式一
执行方法
方式一
注意:方式一,namespace和Xxxmapper可以不同,但是在sqlssion.xxx,中和xml文件一致就可以了
方式二(MyBatis映射器)
加一个接口和命名空间一样,与xml文件相连
public interface CustomerMapper {List<Customer> selectCustomer();
}
或者注解
public interface CustomerMapper {@Select(" SELECT id,user_name,age,pwd,money from users")List<Customer> selectCustomer();
}
//selectList = sqlSession.selectList(NAMESPACE + "selectCustomer");//替换成下面的这个语句CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);selectList = mapper.selectCustomer();
原理 :动态代理,用cglib动态代理实现的。
service层中的改变
写配置文件的映射文件
写了这个还需要写一个命名空间接口,其中的方法对应id的值,那个接口也可以不用写,写是为了和service结合,这里不用结合,就可以不用写,这个命名空间在这里可以写此个xml文件名,
还需要写一个实体类,对应查询回来的数据对应resultType的值,
没有配置简写,就要写完全。
Mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间 ,sql坐标(SQL在哪儿)规范写法:一般写这个mapper.xml所在的包的路径
-->
<mapper namespace="接口,需要实现的方法的接口MxxMapper,">
<!-- 查询语句 id是sql的id,在同一个mapper.xml中必须唯一如何精准的找到一个SQL namespace+idresultType:返回的结果的实体类
--><select id="唯一标识符,对应的是命名空间接口中的某个方法" resultType="返回的实体类路径" ParameterType="参数类型"></select></mapper>
设置对象的别名(简写)
自己写的类配置别名
在MyBatis-Config.xml配置
<!-- 加载properties--><properties resource="jdbc.properties" /><typeAliases><!--给单个类设置别名 alias的值可以任意写--><typeAlias type="com.ronghuanet.mybatis.domain.Product" alias="Product" /><!--扫描包,这个包下面的所有类都设置一个别名-->
<!-- <package name="com.ronghuanet.mybatis.domain"/>--></typeAliases>.........
框架含有的
获取自动生成的主键
先设置这个
<!--自动生成主键赋值给对象的对应的成员变量,再插入进去--><insert ...... useGeneratedKeys="true" 开启自动生成主键 keyColumn="id" 数据库主键列,告诉框架id列 keyProperty="id" 与数据库对应主键的成员变量></insert>
再插入之后,在获取主键,就可以获取到。
查询结果和java的映射规则
注意:查询返回的类型必须要有无参构造。
可以映射的东西: Java 基本数据类型、自定义的 Java 类,以及 java.util.Map(HashMap)等
基本类型映射:
当查询的结果只有一个列时,MyBatis 可以将该列的值直接映射到一个基本数据类型或包装类(如 int、String、Long 等)。
简单对象映射:
当查询的结果包含多个列时,可以通过在映射文件中使用 标签来定义对象映射规则。通过指定结果列和 Java 对象属性的对应关系,MyBatis 将查询结果映射到一个简单对象(POJO)中。
<resultMap id="baseResultMap(唯一表示符)" type="映射的java类型"><id column="id" 数据库列 property="id" 对象属性 /><result column="product_name" 数据库列 property="productName" 对象属性/></resultMap>
将resultType改成resultMap
嵌套对象映射:
如果查询的结果包含多个表的数据,可以使用 和 标签来进行嵌套对象映射。通过在映射文件中定义关联关系,MyBatis 可以将查询结果映射到一个复杂的对象图中,包含多个对象之间的关联关系。
注意: 最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。
使用时避免使用 n+1
原理:list肯很大,list是主要 需要展示的对象
忽略大小写:
MyBatis 在进行列名和属性名的匹配时,默认是忽略大小写的。这意味着列名和属性名在大小写上不区分,可以自动匹配成功。
原理:MyBatis 在进行自动映射时,会根据 Java 对象中的 Setter 方法(即属性的设置方法)来确定属性名,并将查询结果集中的列值赋给该属性。具体来说,MyBatis 会按照以下规则生成属性名:
setName(去掉set)->Name(N变小写)->name 与数据库的字段列一样就好了
下划线转驼峰:
MyBatis 支持将下划线分隔的列名转换为驼峰式命名的属性名。例如,如果查询结果的列名是 user_name,而对象中的属性名是 userName,
则 MyBatis 可以自动将其映射起来。这个需要开启
<settings><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
- 类型处理器:在自动映射时,如果数据库中的数据类型和 Java 对象属性的类型不匹配,MyBatis 会尝试使用类型处理器进行转换。
构造器映射:
除了使用默认的无参构造器来创建对象,MyBatis 还支持使用带参构造器来创建对象,并通过列值来调用相应的构造器。
-
Java 对象必须具有带参构造器:要使用构造器映射,Java 对象必须定义了带参数的构造器。
-
构造器的参数顺序和类型要与查询结果的列对应:构造器的参数顺序和类型必须和查询结果的列一一对应。MyBatis 会根据列名和构造器参数名的匹配关系来确定调用哪个构造器。
<resultMap id="userResultMap" type="User"><constructor><idArg column="id" javaType="Long" /><arg column="username" javaType="String" /><arg column="age" javaType="Integer" /></constructor>
</resultMap>
注解映射:
方式一
除了 XML 配置文件外,MyBatis 还支持使用注解来定义对象映射规则。通过在 Java 对象上添加 @Results 和 @Result 注解,可以实现对象与查询结果的映射。
public class User {@Result(column = "user_id", property = "userId")private Long userId;@Result(column = "username", property = "username")private String username;// 省略 getter 和 setter 方法
}或者
@Results({@Result(column = "user_id", property = "userId"),@Result(column = "username", property = "username")
})
@Select("SELECT user_id, username FROM users")
List<User> getUsers();
方式二
在注解映射中,我们可以使用注解来描述实体类(Java 对象)与数据库表的对应关系,以及实体类的属性与表字段的对应关系。通过这些注解的配置,框架可以自动完成数据库的增删改查操作,并将查询结果映射到对应的 Java 对象中。
- @Entity:用于标识实体类,表示该类与数据库表对应。
- @Table:用于指定实体类与数据库表的映射关系,可以指定表名、schema 等信息。
- @Column:用于指定实体类的属性与数据库表字段的映射关系,可以指定字段名、长度、默认值等信息。
- @Id:用于指定实体类的主键属性,表示该属性是数据库表的主键。
- @GeneratedValue:用于指定主键的生成策略,如自增长、UUID 等。
- @Transient:用于指定实体类的属性不与数据库表字段进行映射,即忽略该属性。
@Entity
@Table(name = "user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "username")private String username;@Column(name = "age")private Integer age;// 省略 getter 和 setter 方法
}
三个关键类的生命周期
1.1.SqlSessionFactoryBuilder
这个类可以被实例化,使用和丢弃。一旦你创建了 SqlSessionFactory 后,这个类就不需要存在了。(因为工厂创建了,工人就不需要了)
1.2.SqlSessionFactory
一旦被创建,SqlSessionFactory 应该在你的应用执行期间都存在,因为是工厂,需要不断创建sqlsession,它应该是单例的也是线程安全的,并且随着应用的创建而创建,随着应用的销毁而销毁。SqlSessionFactory 中存储了:1.xml基本信息,2.连接池对象, 3.二级缓存。SqlSession是通过SqlSessionFactory来创建的。
1.3.SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能被共享,也是线程不安全的。因此最佳的范围是请求或方法范围。它内部存储的东西**:1.连接对象 , 2.一级缓存。**
日志
日志等级
trace(最小,全部都要打印) ,debug(调试) , info(提示) , warn(警告) ,error(错误)
配置
创建一个log4j的配置文件
将配置文件放置在类路径下:将创建好的 Log4j 配置文件放置在项目的类路径下,MyBatis 会在初始化过程中自动加载类路径下的 Log4j 配置文件,并根据配置文件中的设置来进行日志输出
一部分配置配什么查什么
#全局日志配置
log4j.rootLogger = info,console#输出局部的日志信息(自己的包中的日志打印方式)
log4j.logger.com.wj=TRACE 自己哪里的数据操作需要打印操作### 配置输出到控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
### 使用System.out打印日志
log4j.appender.console.Target = System.out
### 指定日志的格式布局(日志是有格式的)
log4j.appender.console.layout = org.apache.log4j.PatternLayout
### 日志的打印格式
log4j.appender.console.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{1}:%L - %m%n
打印入参
进入每一个方法时都可以写,这样可以更好的理解程序的执行过程和传入的数据情况。
#{}与${}比较
其实#{}的底层就是使用的 “?”占位符的方式来拼接SQL,而${}使用的是直接把拼接到SQL中
看看有什么不一样
<select id="selectById" parameterType="long" resultType="...Employee">select * from employee where id = #{id}</select>
-----------------------------------------------------------------------------------------------------<select id="selectById" parameterType="long" resultType="...Employee">select * from employee where id = ${id}</select>
直接拼接会调用 g e t t e r 方法去获取值,会用属性值用于 D D L 可以用再 {} 直接拼接 会调用getter方法去获取值,会用属性值 用于DDL 可以用再 直接拼接会调用getter方法去获取值,会用属性值用于DDL可以用再{group by}
#{} 占位符 不用要用getter方法获取 DQL
使用的场景
${} 适用于静态的表名、列名等,以及一些需要进行复杂表达式的参数替换,使用时需要注意 SQL 注入风险。
#{} 主要用于替换动态生成的参数值,通常在查询条件、插入或更新的参数中使用。提高安全和速度。
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
动态SQL
if
可以匹配多个分支
<if test="title != null">AND title like #{title}</if>
注意
如果用 < 这是一个标签开始符号,
要改成 转义符
如果用要用where
解决一:
在第一个if标签中加where
再在最开始的表名后面 1+1 但是这样就不会用索引了
解决二:
直接用where标签把他们嵌套进去
choose、when、otherwise
只会选择最先匹配的一个分支执行,匹配后不会再进行匹配了
<select id="findActiveBlogLike"resultType="Blog">SELECT * FROM BLOG WHERE state = ‘ACTIVE’<choose><when test="title != null">AND title like #{title}</when><when test="author != null and author.name != null">AND author_name like #{author.name}</when><otherwise>AND featured = 1</otherwise></choose>
</select>
trim、where、set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
trim:通过自定义 trim 元素来定制 where 和set元素的功能。
set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
例如:set
这个set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号<set><if test="username != null">username=#{username},</if><if test="password != null">password=#{password},</if><if test="email != null">email=#{email},</if><if test="bio != null">bio=#{bio}</if></set>等价<trim prefix="SET" suffixOverrides=",">...
</trim>
例如:WHERE
若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。<where><if test="state != null">state = #{state}</if><if test="title != null">AND title like #{title}</if><if test="author != null and author.name != null">AND author_name like #{author.name}</if></where>等价
<trim prefix="WHERE" prefixOverrides="AND |OR ">...
</trim>
foreach(批量操作)
数据最多不能超过4M
如 批量删除
常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
注意当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
它允许你指定一个集合,声明可以在元素体内使用的集合项(集合里的对象)(item)和索引(index)变量
。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符
WHERE ID in<foreach item="item" index="index" collection="list"open="(" separator="," close=")">#{item}</foreach>
如 批量插入需求
上下不匹配
bind
给一段语句设置一个变量
bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文
OGNL 表达式的作用:可以存取对象的属性和调用对象的方法,通过OGNL 表达式可以迭代获取对象的结构图
<select id="selectBlogsLike" resultType="Blog"><bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />SELECT * FROM BLOGWHERE title LIKE #{pattern}
</select>
java API_MyBatis
SqlSessionFactoryBuilder
自我感觉这个类不太重要
SqlSessionFactoryBuilder 有五个 build() 方法,每一种都允许你从不同的资源中创建一个 SqlSessionFactory 实例。
第一种方法是最常用的,它接受一个指向 XML 文件
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
Resources 工具类
URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)
SqlSessionFactory
SqlSessionFactory 有六个方法创建 SqlSession 实例。通常来说,当你选择其中一个方法时,你需要考虑以下几点:
- 事务处理:你希望在 session 作用域中使用事务作用域,还是使用自动提交(auto-commit)?(对很多数据库和/或 JDBC 驱动来说,等同于关闭事务支持)
- 数据库连接:你希望 MyBatis 帮你从已配置的数据源获取连接,还是使用自己提供的连接?
- 语句执行:你希望 MyBatis 复用 PreparedStatement 和/或批量更新语句(包括插入语句和删除语句)吗?
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
ExecutorType.SIMPLE:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。
ExecutorType.REUSE:该类型的执行器会复用预处理语句。
ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解
SqlSession
游标(Cursor)与列表(List)返回的结果相同,不同的是,游标借助迭代器实现了数据的惰性加载。
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {try (Cursor<User> cursor = sqlSession.selectCursor("com.example.UserMapper.getAllUsers")) {Iterator<User> iterator = cursor.iterator();while (iterator.hasNext()) {User user = iterator.next();// 处理 user 数据}}
}
游标:使用游标,查询结果不会一次性全部加载到内存中,而是通过迭代器逐行获取,这可以减少内存占用,尤其在处理大型结果集时更为有效。
列表:列表返回的方式是将查询结果集全部加载到内存的 List 集合中,这使得你可以立即访问整个结果集,但也可能导致内存占用较大,尤其是在处理大量数据时。列表方式适合于小规模的结果集,可以一次性加载并在内存中进行操作。
select 方法的三个高级版本,它们允许你限制返回行数的范围,或是提供自定义结果处理逻辑,通常在数据集非常庞大的情形下使用
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
RowBounds 参数会告诉 MyBatis 略过指定数量的记录,并限制返回结果的数量。RowBounds 类的 offset 和 limit 值**只有在构造函数**时才能传入,其它时候是不能修改的。
int offset = 100; 开始的点
int limit = 25; 限制的行
RowBounds rowBounds = new RowBounds(offset, limit);
数据库驱动决定了略过记录时的查询效率。为了获得最佳的性能,建议将 ResultSet 类型设置为 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE
SCROLL_SENSITIVE:滚动敏感,可以接收其他事务对数据的更改
SCROLL_INSENSITIVE:滚动不敏感
SQL类
JAVA生成代码
public class UserSqlProvider {
public String getUsers(String username, String email) {return new SQL().SELECT("id, username, email").FROM("users").WHERE().<if test="username != null">AND username = #{username}</if>.<if test="email != null">AND email = #{email}</if>.toString();}
}//使用
@SelectProvider(type = UserSqlProvider.class, method = "getUsers")
List<User> getUsers(@Param("username") String username, @Param("email") String email);
踩坑
这样写IStudentService studentService = new StudentServiceImpl();static {try {SqlSession sqlSession = MyBatisUtils.getSqlSession();IDepartmentMapper departmentMapper = sqlSession.getMapper(IDepartmentMapper.class);} catch (Exception e) {e.printStackTrace();}}
不能
会报错,excutor。。被关闭了IStudentService studentService = new StudentServiceImpl();static {try( SqlSession sqlSession = MyBatisUtils.getSqlSession()) {IDepartmentMapper departmentMapper = sqlSession.getMapper(IDepartmentMapper.class);} catch (Exception e) {e.printStackTrace();}}