Mybatis3
Mybatis
作用域和生命周期
- SqlSessionFactoryBuilder
最佳作用域是方法作用域,一旦创建了SqlSessionFactory就不再需要它了。可以重用SqlSessionFactoryBuilder来创建多个SqlSessionFactory实例,但是不要一直保留SqlSessionFactoryBuilder。
- SqlSessionFactory
一旦被创建就应在应用运行期间一直存在,避免多次创建SqlSessionFactory,作用域是应用作用域。
- SqlSession
每个线程都有它自己的SqlSession实例,不是线程安全的,不能共享,最佳的作用域是请求域或者方法域。
//标准用法 try(SqlSession session = sqlSessionFactory.openSession()){ }
- 映射器实例
绑定一些映射语句的接口,映射器的接口实例是从SqlSession中获得的。最好的作用于就是方法作用域。
try(SqlSession session = sqlSessionFactory.openSession()){ BlogMapper mapper = session.getMapper(BlogMapper.class); }
Mabatis配置文件的顶层结构
SqlSessionFactoryBuilder拿着配置文件去创建SqlSessionFactory
属性(Properties)
<properties resource="org/mybatis/example/config.properties"> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
设置好的属性可以在整个配置文件中替换需要动态配置的值。其中${}里面表达式的值可以通过上面的Properties标签设置的值也可以从config.properties文件中读取相应的配置信息
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
默认属性(MyBatis >= 3.4.2 )
<properties resource="org/mybatis/example/config.properties"> <!-- ... --> <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 --> <property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' --> </properties>
如果属性名中包含':'那么按如下设置
<properties resource="org/mybatis/example/config.properties"> <!-- ... --> <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 --> <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 --> </properties> <property name="username" value="${db:username?:ut_user}"/>
设置(Setting)
类型别名(typeAliases)
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
可以从写已有的类型处理器或者创建自己的类型处理器。
- 实现org.apache.ibatis.type.TypeHandler接口
- 继承org.apache.ibatis.type.BaseTypeHandler
package cn.pickle; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @author Pickle * @version V1.0 * @date 2022/12/13 20:34 */ @MappedJdbcTypes(JdbcType.VARCHAR) public class ExampleTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException { preparedStatement.setString(i, s); } @Override public String getNullableResult(ResultSet resultSet, String s) throws SQLException { return resultSet.getString(s); } @Override public String getNullableResult(ResultSet resultSet, int i) throws SQLException { return resultSet.getString(i); } @Override public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException { return callableStatement.getString(i); } }
<!-- mybatis-config.xml --> <typeHandlers> <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/> </typeHandlers>
使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。
处理枚举类型
定义枚举处理器,全局适用
<!-- mybatis-config.xml --> <typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/> </typeHandlers>
自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler
来处理枚举类型, 所以如果我们想用普通的 EnumTypeHandler
,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。
对象工厂
Mybatis在创建结构对象的新实例的时候会用ObjectFactory实例来完成实例化工作。可以扩展DefaultObjectFactory
来自定义对象工厂。
// ExampleObjectFactory.java public class ExampleObjectFactory extends DefaultObjectFactory { @Override public <T> T create(Class<T> type) { return super.create(type); } @Override public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } @Override public void setProperties(Properties properties) { super.setProperties(properties); } @Override public <T> boolean isCollection(Class<T> type) { return Collection.class.isAssignableFrom(type); }}
<!-- mybatis-config.xml --> <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory>
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
插件
环境配置(Environment)
每个SqlsessionFactory
实例只能选择一种环境
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
- 默认使用的环境ID(比如:defalut="development")
- 每个environment元素定义的环境ID(比如:id="development")
- 事务管理器的配置(比如:type="JDBC")
- 数据源的配置(比如:type="POOLED")
事务管理器
- JDBC
使用JDBC的提交和回滚
- MANAGED
不做任何事情
数据源
- UNPOOLED
每次请求打开和关闭连接
- POOLED
利用池化思想将JDBC连接对象组织起来,避免创建新的连接是所需要的初始化和认证时间。
- JNDI
XML映射器
Select
Select 元素属性
<select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
<select id="selectStudent" resultType="student"> Select * from student where id = #{id}; </select>
{id}
告诉Mybatis创建一个预处理语句
// 近似的 JDBC 代码,非 MyBatis 代码... String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(selectPerson); ps.setInt(1,id);
在Intert操作中设置主键
<insert id="insertAuthor"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 </selectKey> insert into Author (id, username, password, email,bio, favourite_section) values (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR}) </insert>
<selectKey keyProperty="id" resultType="int" order="BEFORE" statementType="PREPARED">
在Insert的时候需要在可能为空的列上加上jdbcType。
字符串替换
用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。
结果映射
对简单的语句做到零配置,对复杂的语句,只需要描述语句之间的关系就行了。
<!-- mybatis-config.xml 中 --> <typeAlias type="com.someapp.model.User" alias="User"/> <!-- SQL 映射 XML 中 --> <select id="selectUsers" resultType="User"> select id, username, hashedPassword from some_table where id = #{id} </select>
Mybatis会利用一个ResultMap根据属性名来映射到JavaBean上,如果列明和属性名不匹配,可以在Select语句中设置别名。
<select id="selectUsers" resultType="User"> select user_id as "id", user_name as "userName", hashed_password as "hashedPassword" from some_table where id = #{id} </select>
使用ResultMap
<resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap> <select id="selectUsers" resultMap="userResultMap"> select user_id, user_name, hashed_password from some_table where id = #{id} </select>
Id&result
id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。
- properties
映射到结果的字段或属性,先去寻找properties同名的属性,再去寻找给定名称的字段field
(成员变量)。
- column
数据库列名或者列的别名。
- javaType
一个Java类的全限定名
- jdbcType
只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
- typeHandler
类型处理器。
构造方法
关联的嵌套Select查询
<resultMap id="blogResult" type="Blog"> <association property="author" column="author_id" javaType="Author" select="selectAuthor"/> </resultMap> <select id="selectBlog" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectAuthor" resultType="Author"> SELECT * FROM AUTHOR WHERE ID = #{id} </select>
我们有两个 select 查询语句:一个用来加载博客(Blog),另外一个用来加载作者(Author),而且博客的结果映射描述了应该使用 selectAuthor 语句加载它的 author 属性。
其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。
缓存
在Sql映射文件中加入<cache/>
缓存只存在于 <cache/>
标签所在的映射文件的语句
在使用缓存时
entity
类需要实现Serializable
接口否则就会报错
Sql语句构造器
从版本 3.4.2 开始,你可以像下面这样使用可变长度参数:
public String selectPersonSql() { return new SQL() .SELECT("P.ID", "A.USERNAME", "A.PASSWORD", "P.FULL_NAME", "D.DEPARTMENT_NAME", "C.COMPANY_NAME") .FROM("PERSON P", "ACCOUNT A") .INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID", "COMPANY C on D.COMPANY_ID = C.ID") .WHERE("P.ID = A.ID", "P.FULL_NAME like #{name}") .ORDER_BY("P.ID", "P.FULL_NAME") .toString(); } public String insertPersonSql() { return new SQL() .INSERT_INTO("PERSON") .INTO_COLUMNS("ID", "FULL_NAME") .INTO_VALUES("#{id}", "#{fullName}") .toString(); } public String updatePersonSql() { return new SQL() .UPDATE("PERSON") .SET("FULL_NAME = #{fullName}", "DATE_OF_BIRTH = #{dateOfBirth}") .WHERE("ID = #{id}") .toString(); }
3.5.2开始可以批量插入
public String insertPersoinSql(){ //INSERT INTO PERSON(ID,FULL_NAME) // VALUES(#{mainPerson.id},#{mainPerson.fullName}),(#{sunPerson.id},#{subPerson.fullName}) return new SQL() .INSERT_INTO("PERSON") .INTO_COLUMS("ID","FULL_NAME") .INTO_VALUES("#{mainPerson.id}","#{mainPerson.fullName}") .ADD_ROW() .INTO_VALUES("#{subPerson.id}","#{subPerson.fullName}") .toString(); }
日志
要在Mybatis中使用日志,需要在
mybatis-config.xml
的<setting/>
标签中配置
- STDOUT_LOGGING 标准日志
- LOG4J Java日志(Log for Java)
- LOG4J2 船新版本
- SLF4J 简单日志门面 (Simple Logging for Java)
未配置日志前的查询输出
STDOUT_LOGGING
<settings> <!-- 标准日志工厂实现 --> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
配置了日志的输出
按照图中标出的数据
- Opening JDBC ---- 打开Jdbc连接
- Created connection ---- 创建连接
其中箭头指出的显示将autocommit to false
是因为我们在配置文件中配置事务管理的时候设置为true
- 之后就是查询的Sql语句和返回的结果
Log4J
- Log4j 是 Apache 的一个开源项目,通过使用 Log4j ,可以控制日志信息输送的目的地是控制台、文件、GUI 组件、甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等
- 也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
- 这些都可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
导入Maven坐标
<!-- log4j 日志依赖 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
配置文件
log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/qiyuan.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
mybatis-config.xml
<setting> <!--使用 log4j 注意不能打错--> <setting name="logImpl" value="LOG4J"/> </setting>
查询后的输出
可以看到在Reader entry的时候出现了乱码,这是因为我们用了
<typeAliases>
设置类别名导致的,把使用别名的地方改一下,就不会出现乱码了。
注释<typeAliases></typeAliases>
,
Mapper
文件中改为全限定名
再次查询,乱码不见了
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术