开发时一般采用xml开发,写动态sql时xml开发更容易
作用域(Scope)和生命周期
理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
提示对象生命周期和依赖注入框架
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量),生命周期也就只存在方法内部
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
相当于JDBC的connection,每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。因此最佳生命周期是一个方法或一次请求 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
映射器--Mapper
映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}
实体类与表中字段名不一样(不清楚用处)
通过sql语句定义别名
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
resultMap映射
<select id="getOrder" parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
<!–用id属性来映射主键字段–>
<id property="id" column="order_id">
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
<result property ="orderno" column ="order_no"/>
<result property="price" column="order_price" />
</reslutMap>
Mybatis是否映射Enum
可以
#{}与${}
首先Mybatis处理#{},#{}传入参数是以字符串传入,会将SQL的#{}替换为?,调用PreparedStatement的set方法赋值
然后介绍二者
- #{}可以防止SQL注入;${}不能
- #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译
- #{}变量替换在DBMS中,${}变量替换是在DBMS外
模糊查询like
'%${name}%'--会造成sql注入问题
传入"sql注入%' or '1%' = '1"时查询成功
<select id="getByName" resultType="com.pojo.Book">
select * from tbl_book where name like '%${name}%'
</select>
"%"#{name}"%"--#{}解析成sql语句变量外侧自动加单引号' '
<select id="getByName" resultType="com.pojo.Book">
select * from tbl_book where name like "%"#{name}"%"
</select>
推荐--CONCAT('%',#{name},'%')--CONCAT()函数
<select id="getByName" resultType="com.pojo.Book">
select * from tbl_book where name like CONCAT('%',#{name},'%')
</select>
mybatis能一对一,一对多关联查询
动态sql
刚开始我在想这个东西有什么意义,业务层不也能判断吗,后来发现能节省,
如果你在业务层判断,那么需要至少两个sql语句,mapper的方法和xml至少得两个,如果业务逻辑复杂,到时候就是噩梦,但是动态sql就可以减少这种不必要的麻烦
if每个都会判断,外面需要加where
<if>
<select id="getBookWithSome" resultType="com.pojo.Book">
select * from tbl_book
<where>
<if test="type != null and type!=''">
AND type like concat('%',#{type},'%')
</if>
<if test="name != null and name!=''">
AND name like concat('%',#{name},'%')
</if>
<if test="description != null and description!=''">
AND description like concat('%',#{description},'%')
</if>
</where>
</select>
choose (when, otherwise)
when一旦匹配则立刻退出,外面需要加choose
<select id="getBookWithSome" resultType="com.pojo.Book">
select * from tbl_book
<where>
<choose>
<when test="type != null and type!=''">
AND type like concat('%',#{type},'%')
</when>
<when test="name != null and name!=''">
AND name like concat('%',#{name},'%')
</when>
<when test="description != null and description!=''">
AND description like concat('%',#{description},'%')
</when>
</choose>
</where>
</select>
<set>
<set>:常用于<update>更新语句中,替代 sql中的“set”关键字,特别是在联合<if>进行判断是,可以有效方式当某个参数为空或者不合法是错误的更新到数据库中
<update id="updateByPrimaryKeySelective" parameterType="pojo.Orderitem" >
update orderitem
<set >
<if test="productId != null" >
product_id = #{productId,jdbcType=VARCHAR},
</if>
<if test="count != null" >
count = #{count,jdbcType=INTEGER},
</if>
</set>
where orderitem_id = #{orderitemId,jdbcType=VARCHAR}
</update>
<where>可以用在所有的查询条件都是动态的情况
trim (where, set)
他可以实践where和set的效果
<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
prefix:在trim标签内sql语句加上前缀。
suffix:在trim标签内sql语句加上后缀。
suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。
prefixOverrides:指定去除多余的前缀内容
下面是一个往购物车表中插入数据的mybatis语句
<insert id="insert" parameterType="com.tortuousroad.groupon.cart.entity.Cart">
insert into cart
<trim prefix="(" suffix=")" suffixOverrides=","> (把 尾部的,替换成 ")")
<if test="id != null">
id,
</if>
<if test="userId != null">
user_id,
</if>
<if test="dealId != null">
deal_id,
</if>
<if test="dealSkuId != null">
deal_sku_id,
</if>
<if test="count != null">
count,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=BIGINT},
</if>
<if test="userId != null">
#{userId,jdbcType=BIGINT},
</if>
<if test="dealId != null">
#{dealId,jdbcType=BIGINT},
</if>
<if test="dealSkuId != null">
#{dealSkuId,jdbcType=BIGINT},
</if>
<if test="count != null">
#{count,jdbcType=INTEGER},
</if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
foreach-遍历
mybatis的一级缓存和二级缓存
一级缓存
- 默认开启一级缓存,是基于PerpetualCache的HashMap本地缓存,各个SqlSession之间的缓存相互隔离,SqlSession 只是一个 MyBatis 对外的接口,SqlSession 将它的工作交给了 Executor 执行器这个角色来完成,负责完成对数据库的各种操作。当创建了一个 SqlSession 对象时,MyBatis 会为这个 SqlSession 对象创建一个新的 Executor 执行器,而缓存信息就被维护在这个 Executor 执行器中,MyBatis 将缓存和对缓存相关的操作封装在 Cache 接口中。
当SqlSession调用close()会释放掉一级缓存 PerpetualCache 对象,clearCache(),任何一个增删改后提交事务,都会清空 PerpetualCache 对象中的数据
二级缓存
- 默认不开启二级缓存,要开启二级缓存。如果开启了二级缓存,查询的顺序是二级缓存 → 一级缓存 → 数据库。二级缓存作用于全局,对于一些相当消耗性能的,并且对于时效性不敏感的查询可以使用二级缓存。
开启
xml
1.全局变量开启二级缓存
STDOUT_LOGGING是idea自带的日志输出,小项目够用了,如果是大型项目,涉及到千万级数据库信息,那就要log4j了
<settings>
<!-- 开启日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启二级缓存(整体开启) -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.在具体Mapper映射文件的命名空间进行二级缓存配置
<mapper namespace="com.mapper.BookDao">
<!-- cacahe节点:配置当前命名空间下mapper的缓存 -->
<cache eviction= "LRU" flushInterval= "100000" readOnly= "true" size= "1024"/>
<select id="getById" resultType="com.pojo.Book">
select * from tbl_book where id = #{id}
</select>
</mapper>
其他mapper也使用
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
注解
- 注解方式时,追加 @CacheNamespace
Mapper传参
id应该与方法名一致
顺序传参#{0}#{1}
public List<Book> getByPage1(Integer pageNum, Integer pageSize);
<select id="getByPage1" resultType="com.pojo.Book">
select * from tbl_book limit #{arg0},#{arg1}
</select>
#{}的数字表示传入参数的顺序(不建议)
@Param注解传参
public List<Book> getByPage2(@Param("num") int pageNum, @Param("size") int pageSize);
<select id="getByPage2" parameterType="com.pojo.PageInfo" resultType="com.pojo.Book">
select * from tbl_book limit #{num},#{size}
</select>
#{}里的名称对应注解里的名称(参数不多情况下,推荐)
不加注解时,直接写对应参数名称也可以获取,但是加上注解后,该参数只能通过注解里的名称获取
Map传参(不推荐)
public List<Book> getByPage3(Map<String, Object> params);
<select id="getByPage3" parameterType="java.util.Map" resultType="com.pojo.Book">
select * from tbl_book limit #{pageNum},#{pageSize}
</select>
#{}里是map的key(必须一致),适合多个参数,
Java Bean传参
public List<Book> getByPage4(PageInfo pageInfo);
<select id="getByPage4" parameterType="com.pojo.PageInfo" resultType="com.pojo.Book">
select * from tbl_book limit #{pageNum},#{pageSize}
</select>
@Data
public class PageInfo {
//当前页起始
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
}
#{}里写成员属性,前提需要实体类,推荐
XML配置案例
1.导入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
2.配置Mapper.xml文件
<?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方法的dao层,就是这个Mapper.xml的身份标签-->
<mapper namespace="com.mapper.BookDao">
<!--sql语句是select类型,标签用select,
id随便起,是这条sql语句的身份标签,一个xml文件里可以有多个sql语句,但是id不能重复
resultType是执行sql的结果,mybatis底层会把statement执行sql的结果集解析出来。用set方法封装到User对象里,因为User的变量名和数据库列名一样,所以通过反射setXx可以封装成功
-->
<select id="getById" resultType="com.pojo.Book">
select * from tbl_book where id = #{id}
</select>
</mapper>
3.配置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">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--dataSource的类型可以配置成其内置类型之一,如UNPOOLED、POOLED、JNDI。
UNPOOLED,mybaties会为每一个数据库操作创建一个新的连接,并关闭它。该方式适用于只有小规模数量并发用户的简单应用程序上。
POOLED,mybaties会创建一个数据库连接池,连接池的一个连接将会被用作数据库操作。一旦数据库操作完成,mybaties会将此连接返回给连接池。在开发或测试环境中经常用到此方式。
JNDI。mybaties会从在应用服务器向配置好的JNDI数据源DataSource获取数据库连接。在生产环境中优先考虑这种方式。-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm_db?useSSL=false&useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 把上面的Mapper.xml 注册进来,路径写在resources目录下的路径-->
<mapper resource="com/mapper/Mapper.xml"/>
</mappers>
</configuration>
4.测试
/**
* @author 伍六七
* @date 2022/7/18 19:30
*/
public class MyTest {
@Test
public static void main(String[] args) {
try {
//1.创建sqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.sqlSessionFactory生产Sqlsession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.执行mapper的sql语句
Book book = sqlSession.selectOne("com.mapper.BookDao.getById", 2);
System.out.println(book);
//如果是更新,删除,记得提交
//sqlSession.commit();
//4.关闭IO资源(工厂对象会自动回收)
inputStream.close();
sqlSession.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
红色提示可以将driver的value改为com.mysql.cj.jdbc.Driver
详解
Mapper.xml
两部分:xml头部的声明和mapper部分
xml头部的声明
<?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">
mapper部分
<!--NameSpace 任意起名字,就是这个Mapper.xml的身份标签-->
<mapper namespace="com.mapper.BookDao">
<!--sql语句是select类型,标签用select,
id随便起,是这条sql语句的身份标签,一个xml文件里可以有多个sql语句,但是id不能重复
resultType是执行sql的结果,mybatis底层会把statement执行sql的结果集解析出来。用set方法封装到User对象里,因为User的变量名和数据库列名一样,所以通过反射setXx可以封装成功
-->
<select id="getById" resultType="com.pojo.Book">
select * from tbl_book where id = #{id}
</select>
</mapper>
mybatis-config.xml
xml头部的声明和数据库部分
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">
数据库部分
- environment 元素体中包含了事务管理和连接池的配置。
- mappers 元素则包含了一组映射器(mapper)
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--dataSource的类型可以配置成其内置类型之一,如UNPOOLED、POOLED、JNDI。
UNPOOLED,mybaties会为每一个数据库操作创建一个新的连接,并关闭它。该方式适用于只有小规模数量并发用户的简单应用程序上。
POOLED,mybaties会创建一个数据库连接池,连接池的一个连接将会被用作数据库操作。一旦数据库操作完成,mybaties会将此连接返回给连接池。在开发或测试环境中经常用到此方式。
JNDI。mybaties会从在应用服务器向配置好的JNDI数据源DataSource获取数据库连接。在生产环境中优先考虑这种方式。-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm_db?useSSL=false&useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 把上面的Mapper.xml 注册进来,路径写在resources目录下的路径-->
<mapper resource="com/mapper/Mapper.xml"/>
</mappers>
</configuration>
注解入门
1.导入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
2.配置文件 mybatis-config.xml
注意内容(name)与xml开发(resource)不同
<mappers>
<!-- 该路径为java项目里文件路径,注意与xml开发区别-->
<package name="com.mapper"/>
</mappers>
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--dataSource的类型可以配置成其内置类型之一,如UNPOOLED、POOLED、JNDI。
UNPOOLED,mybaties会为每一个数据库操作创建一个新的连接,并关闭它。该方式适用于只有小规模数量并发用户的简单应用程序上。
POOLED,mybaties会创建一个数据库连接池,连接池的一个连接将会被用作数据库操作。一旦数据库操作完成,mybaties会将此连接返回给连接池。在开发或测试环境中经常用到此方式。
JNDI。mybaties会从在应用服务器向配置好的JNDI数据源DataSource获取数据库连接。在生产环境中优先考虑这种方式。-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm_db?useSSL=false&useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.mapper"/>
</mappers>
</configuration>
3.实现方法+sql工厂
方法
/**
* @author 伍六七
* @date 2022/7/18 19:34
*/
public interface BookDao {
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
}
sql工厂
/**
* @author 伍六七
* @date 2022/7/18 20:42
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
4.调用方法
该调用方法注解和xml都可以使用
/**
* @author 伍六七
* @date 2022/7/18 19:30
*/
public class MyTest {
@Test
public static void main(String[] args) {
//注解实现
SqlSession sqlSession = MybatisUtils.getSqlSession();
BookDao mapper = sqlSession.getMapper(BookDao.class);
Book book = mapper.getById(2);
System.out.println(book);
}
}
MybatisUtils-Sqlsession的创建工厂模式
可以分析下如何实现的创建Sqlsession
https://blog.csdn.net/weixin_49149614/article/details/108777283
/**
* @author 伍六七
* @date 2022/7/18 20:42
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
//创建SqlSessionFactory
try {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
//通过SqlSessionFactory创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
Sqlsession的创建--
SqlSession sqlSession = MybatisUtils.getSqlSession();