1.#和$的区别
#{}表示一个占位符号
- 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
- #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。
- 可以自动对值添加 ’ ’ 单引号
${}表示拼接 sql 串
- 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换,
- ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
- 比如order by id 这种的,以id排序 那么这个id 是没有单引号的,就是简单的SQL拼接,所以我们应该使用${} 而不是#{}
多个参数
当我们有多个变量的时候,我们就需要通过arg0,arg1来获取对应的变量了
// 接口方法
int updateNickname( @Param("id") int id,@Param("nickname")String nickname);
<update id="updateNickname">
update t_user set nickname = #{nickname} where id = #{id}
</update>
包装类型单个参数
java有八个基本数据类型,我们在使用这些基本数据类型,可以直接取到对应的value,比如我们上次课程中涉及到的int类型。
但是当我们的参数是包装类型时,比如String或者是User的实体类,这时mybatis就或读取包装类型中的属性,比如我们在使用User实体类时,直接使用实体类中的#{id}属性。但是String类型并没有对应的属性,我们希望的是直接获取String中的值,那么我们在取值的时候,就会出现这个错误。
There is no getter for property named 'xxx' in 'class java.lang.String'
这个报错的意思就是从String类型中获取不到xxx属性
这个使用我们也需要通过@Param的注解来解决这个问题
// 接口方法
List<User> selectList(@Param("orderBy") String orderBy);
<select id="selectList" parameterType="String" resultType="User">
select * from t_user ${orderBy}
</select>
一般我们获取变量值的时候使用#{}来读取属性,如果需要进行sql拼接的时候可以使用${},使用${}的时候要注意防止sql注入。
2.paramerterType和resultType
2.1 paramerterType
SQL 语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)
2.2 resultType
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
2.3resultMap
通过resultMap,我们可以指定查询结果字段和实体属性字段的映射关系。
<resultMap id="userResult" type="User">
<id column="id" property="id" />
<result property="nickname" column="nickname" />
<result property="schoolName" column="school_name" />
</resultMap>
3.mybatis-config.xml 配置文件
3.1属性配置的两种方式
3.1.1直接配置属性
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/test"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="root"/>
</properties>
3.1.2 读取配置文件
创建db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/erp16?useUnicode=true&characterEncoding=UTF-8
username=root
password=root
配置文件读取
<properties resource="db.properties" />
3.2 typeAliases属性
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.tledu.zrz.pojo.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.tledu.zrz.pojo"/>
<package name="其它包"/>
</typeAliases>
3.3mapper属性
Mappers是我们所说的映射器,用于通过mybatis配置文件去找到对应的mapper文件,关于这个属性有三种用法。
3.3.1Resource
使用相对于类路径的资源如:<mapper resource="com/tledu/zrz/dao/IUserDao.xml" />
3.3.2 class
使用 mapper 接口类路径
如:<mapper class="com.tledu.zrz.dao.UserDao"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
3.3.3package
注册指定包下的所有 mapper 接口
如:<package name="com.tledu.zrz.mapper"/>
4.动态sql
4.1 if
<select id="list" parameterType="User" resultMap="userResult">
select * from t_user where 1=1
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="nickname != null and nickname != ''">
and nickname like concat('%',#{nickname},'%')
</if>
</select>
4.2 choose、when、otherwise
<select id="list" parameterType="User" resultMap="userResult">
select * from t_user where 1=1
<choose>
<when test="id != null">
and id = #{id}
</when>
<when test="username != null and username != ''">
and username = #{username}
</when>
<otherwise>
and nickname = #{nickname}
</otherwise>
</choose>
</select>
4.3where
在我们where条件不确定的时候,我们每次都需要加上一个1=1才能保证后面的拼接不会出现错误,使用where标签后,我们就不需要考虑拼接的问题了,直接在里面添加条件即可。通过where标签也可以让代码更具有语义化,方便维护代码
<select id="list" parameterType="User" resultMap="userResult">
select * from t_user where
<where>
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="nickname != null and nickname != ''">
and nickname like concat('%',#{nickname},'%')
</if>
</where>
</select>
4.4set
在进行更新操作的时候,也可以用set标签添加更新条件,同样我们也就不需要在进行字符串拼接了,set标签会帮我们进行拼接。
<update id="updateNickname">
update t_user
<set>
<if test="nickname != null and nickname != ''">
nickname = #{nickname},
</if>
<if test="username != null and username != ''">
username = #{username},
</if>
</set>
where id = #{id}
</update>
4.5foreach
<insert id="batchInsert">
insert into t_user (username, password, nickname) VALUES
<foreach collection="list" index="idx" item="item" separator=",">
(#{item.username},#{item.password},#{item.nickname})
</foreach>
</insert>
in 查询
<select id="list" parameterType="User" resultMap="userResult">
select * from t_user
<where>
<if test="user.username != null and user.username != ''">
and username = #{user.username}
</if>
<if test="user.nickname != null and user.nickname != ''">
and nickname like concat('%',#{user.nickname},'%')
</if>
and id in
<foreach collection="idList" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</where>
</select>
属性说明
- collection 需要遍历的列表
- item 每一项的形参名
- index 每一项索引名
- separtor 分隔符
- open 开始符号
- close 关闭符号
5. 联查
在项目中,某些实体类之间肯定有关联关系,比如一对一,一对多等,在mybatis 中可以通过association和collection,来处理这些关联关系。
5.1 1对1
在实现1对1映射的时候,可以通过association属性进行设置。在这里有三种方式
5.1.1 使用select
<resultMap id="address" type="Address">
<id column="id" property="id" />
<association property="user" column="user_id" javaType="User" select="com.tledu.erp.dao.IUser2Dao.selectById"/>
</resultMap>
- property配置了实体类对应的属性
- column配置了关联字段
- select对应了IUser2Dao中的查询语句
5.1.2直接进行联查,在association中配置映射字段
这里可以直接写联查,需要转换的字段可以在association中进行配置。
<resultMap id="address" type="Address" autoMapping="true">
<id column="id" property="id" />
<association property="user" column="user_id" javaType="User" >
<id column="user_id" property="id" />
<result column="school_name" property="schoolName" />
</association>
</resultMap>
<select id="selectOne" resultMap="address">
select * from t_address left join t_user tu on tu.id = t_address.user_id where t_address.id = #{id}
</select>
autoType代表自动封装,如果不填写,则需要添加所有的对应关系。
5.1.3嵌套的resultType
resultMap id="addressMap" type="Address" autoMapping="true">
<id column="id" property="id"/>
<association property="user" column="user_id" resultMap="userMap">
</association>
</resultMap>
<resultMap id="userMap" type="User" autoMapping="true">
<id column="user_id" property="id" />
<result column="school_name" property="schoolName"/>
</resultMap>
<select id="selectOne" resultMap="addressMap">
select t_address.id,
addr,
phone,
postcode,
user_id,
username,
password,
nickname,
age,
sex,
school_name
from t_address
left join t_user tu on tu.id = t_address.user_id
where t_address.id = #{id}
</select>
5.2 1对多
对于address来说,一个地址对应一个创建用户,但是对于User来说,一个用户可能对应创建了多条地址信息,这种情况, mybatis中就可以通过collections解决。在
同样也是有三种方法
5.2.1 使用select
在AddressDao中添加
<select id="selectByUserId" resultType="Address">
select * from t_address where user_id = #{userId}
</select>
在UserDao中添加
<resultMap id="userResult" type="User" autoMapping="true">
<id column="id" property="id"/>
<result property="nickname" column="nickname"/>
<result property="schoolName" column="school_name"/>
<collection property="addressList" column="id" autoMapping="true" select="com.tledu.erp.dao.IAddressDao.selectByUserId" >
</collection>
</resultMap>
<select id="selectById" parameterType="int" resultMap="userResult">
select *
from t_user
where id = #{id}
</select>
5.2.2 直接进行联查,在collection中配置映射字段
<resultMap id="userResult" type="User" autoMapping="true">
<id column="id" property="id"/>
<result property="nickname" column="nickname"/>
<result property="schoolName" column="school_name"/>
<collection property="addressList" column="phone" ofType="Address" autoMapping="true">
<id column="address_id" property="id" />
</collection>
</resultMap>
<select id="selectById" parameterType="int" resultMap="userResult">
select tu.id,
username,
password,
nickname,
age,
sex,
school_name,
ta.id as address_id,
addr,
phone,
postcode,
user_id
from t_user tu
left join t_address ta on tu.id = ta.user_id
where tu.id = #{id}
</select>
autoMapping代表自动封装,如果不填写,则需要添加所有的对应关系。
这里需要配置ofType来指定返回值类型
这种方式的问题是,当collection需要被多次引用的时候,就需要进行多次重复的配置,所以我们还有第三种方式,引用resultMap。
<resultMap id="userResult" type="User" autoMapping="true">
<!-- <id column="id" property="id"/>-->
<result property="nickname" column="nickname"/>
<result property="schoolName" column="school_name"/>
<collection property="addressList" column="phone" ofType="Address" resultMap="addressResultMap" autoMapping="true">
</collection>
</resultMap>
<resultMap id="addressResultMap" type="Address" autoMapping="true">
<id column="address_id" property="id" />
</resultMap>
<select id="selectById" parameterType="int" resultMap="userResult">
select tu.id,
username,
password,
nickname,
age,
sex,
school_name,
ta.id as address_id,
addr,
phone,
postcode,
user_id
from t_user tu
left join t_address ta on tu.id = ta.user_id
where tu.id = #{id}
</select>
6.mybatis中的连接池
- 连接池是面向数据库连接的
- 连接池是为了优化数据库连接资源
6.1分类
在mybatis中数据库连接池可以分为以下三类
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- JNDI 使用JNDI实现的数据库连接池
6.2连接过程
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
private String driver;
private String url;
private String username;
private String password;
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
public UnpooledDataSource() {
}
public UnpooledDataSource(String driver, String url, String username, String password){
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
public Connection getConnection() throws SQLException {
return this.doGetConnection(this.username, this.password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if(this.driverProperties != null) {
props.putAll(this.driverProperties);
}
if(username != null) {
props.setProperty("user", username);
}
if(password != null) {
props.setProperty("password", password);
}
return this.doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
this.initializeDriver();
Connection connection = DriverManager.getConnection(this.url, properties);
this.configureConnection(connection);
return connection;
}
}
7.mybatis中的事务和隔离级别
7.1jdab中的事务
在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()方法就可以调整。 通过 JDK 文档,我们找到该方法如下:
那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC的 setAutoCommit()方法来设置事务提交方式的。
7.2mybatis中事务提交方式
Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。
//用于在测试方法执行之前执行
@BeforeEach
public void init()throws Exception{
// 设置为自动提交
sqlSession = MybatisUtils.openSession(true);
//获取dao的代理对象
userMapper = sqlSession.getMapper(IUserMapper.class);
}
// 在测试结束之后执行
@AfterEach
public void destroy()throws Exception{
//提交事务
//sqlSession.commit();
//释放资源
sqlSession.close();
}
@Test
public void testCommit(){
User condition = new User();
condition.setId(1);
condition.setNickname("尚云科技1112");
int i = userMapper.update(condition);
assertEquals(i,1);
}
@BeforeEach: 在每个方法之前加一下些操作
@AfterEach: 在每个方法之后加一些操作
这里我们设置了自动提交事务之后,就不需要在进行commit操作了
7.3事务的回滚
对于我们开发过程,也会遇到报错的情况,这个时候为了保证数据的一致性我们就需要进行事务的回滚,比如我们有一个操作需要同时更新用户表和地址表,这个时候更新用户表的时候成功了,但是更新地址表的时候出现了一个特别长的字段,导致更新失败,这个时候,我们需要将数据进行回滚。
7.4事务的隔离级别
涉及到事务这一模块的时候经常会涉及到的三个名词就是
脏读
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问 这个数据,然后使用了这个数据。
不可重复度
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
幻读
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
事务隔离
√: 可能出现 ×: 不会出现
脏读 | 不可重复读 | 幻读 | 说明 | |
Read uncommitted | √ | √ | √ | 直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别 的事务可以读到这个改变.这是很不安全的。允许任务读取数据库中未提交的数据更改,也称为脏读。 |
Read committed | × | √ | √ | 直译就是"读提交",可防止脏读,意思就是语句提交以后即执行了COMMIT以后,别的事务才能读到这个改变. 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 |
Repeatable read | × | × | √ | 直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的.在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。 |
Serializable | × | × | × | 直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行. 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞 |
8.延迟加载策略
8.1定义
延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
8.2 association实现延迟加载
8.2.1未实现延迟加载的时候
我们之前未实现延迟加载的时候,每次操作,都会直接查询出我们需要的数据
8.2.2开启延迟加载
通过lazyLoadingEnabled、aggressiveLazyLoading可以对延迟加载进行配置
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
8.2.3 aggressiveLazyLoading
属性开启之后,我们获取到变量的任意属性,就会触发懒加载,而关闭之后,我们只有触发到关联属性时,才会触发懒加载
8.2.4配置每个关联字段的加载方式
<resultMap id="addressResultMap" type="Address">
<association property="user" column="user_id" javaType="User"
select="com.tledu.erp.mapper.IUserMapper.selectById" fetchType="lazy"/>
</resultMap>
9.使用注解进行开发
9.1mybatis常用注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
9.2实现基本的CRUD
public interface IAddressDao {
@Insert("insert into t_address (addr, phone, postcode, user_id) VALUES (#{addr},#{phone},#{postcode},#{userId})")
int insert(Address address);
@Delete("delete from t_address where id = #{id}")
int delete(int id);
@Update("update t_address set addr = #{addr} where id = #{id}")
int update(Address address);
@Select("select * from t_address where id = #{id}")
Address selectById(int id);
}
9.3使用Result进行映射
@Select("select * from t_address where id = #{id}")
@Results(id = "addressRes", value = {
//id = true 标志这个字段是主键
@Result(id = true, column = "id", property = "id"),
@Result(column = "addr", property = "addr"),
@Result(column = "phone", property = "mobile"),
})
Address selectById(int id);
9.4 注解进行关联查询
9.4.1 1对1
@Select("select * from t_address where id = #{id}")
@Results(id = "addressRes", value = {
//id = true 标志这个字段是主键
@Result(id = true, column = "id", property = "id"),
@Result(column = "addr", property = "addr"),
@Result(column = "phone", property = "mobile"),
@Result(column = "user_id", property = "user",
one = @One(select = "com.tledu.erp.mapper.IUserMapper.selectById", fetchType = FetchType.EAGER))
})
Address selectById(int id);
9.4.2 1对多
@Select("select * from t_user where id = #{id}")
@Results(id = "addressRes", value = {
//id = true 标志这个字段是主键
@Result(id = true, column = "id", property = "id"),
@Result(column = "id", property = "addressList",
many = @Many(select = "com.tledu.erp.mapper.IAddressMapper.listByUserId", fetchType = FetchType.EAGER))
})
User selectById(int id);
10.缓存
10.1一级缓存
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。