其他分享
首页 > 其他分享> > Mybatis学习总结_3_分页

Mybatis学习总结_3_分页

作者:互联网

1 mybatis中的连接池

1.1 什么是连接池

1.2 Mybatis中的连接池

1.3 UNPOOLED连接过程分析

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;
    }
}

如上代码所示,UnpooledDataSource会做以下事情:

从上述的代码中可以看到,我们每调用一次getConnection()方法,都会通过DriverManager.getConnection()返回新的java.sql.Connection实例,所以没有连接池。

1.4 POOLED 数据源 连接池

/*
 * 传递一个用户名和密码,从连接池中返回可用的PooledConnection
 */
private PooledConnection popConnection(String username, String password) throws SQLException
{
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null)
    {
        synchronized (state)
        {
            if (state.idleConnections.size() > 0)
            {
                // 连接池中有空闲连接,取出第一个
                conn = state.idleConnections.remove(0);
                if (log.isDebugEnabled())
                {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                }
            }
            else
            {
                // 连接池中没有空闲连接,则取当前正在使用的连接数小于最大限定值,
                if (state.activeConnections.size() < poolMaximumActiveConnections)
                {
                    // 创建一个新的connection对象
                    conn = new PooledConnection(dataSource.getConnection(), this);
                    @SuppressWarnings("unused")
                    //used in logging, if enabled
                    Connection realConn = conn.getRealConnection();
                    if (log.isDebugEnabled())
                    {
                        log.debug("Created connection " + conn.getRealHashCode() + ".");
                    }
                }
                else
                {
                    // Cannot create new connection 当活动连接池已满,不能创建时,取出活动连接池的第一个,即最先进入连接池的PooledConnection对象
                    // 计算它的校验时间,如果校验时间大于连接池规定的最大校验时间,则认为它已经过期了,利用这个PoolConnection内部的realConnection重新生成一个PooledConnection
                    //
                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    if (longestCheckoutTime > poolMaximumCheckoutTime)
                    {
                        // Can claim overdue connection
                        state.claimedOverdueConnectionCount++;
                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        state.accumulatedCheckoutTime += longestCheckoutTime;
                        state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit())
                        {
                            oldestActiveConnection.getRealConnection().rollback();
                        }
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled())
                        {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    }
                    else
                    {

                        //如果不能释放,则必须等待有
                        // Must wait
                        try
                        {
                            if (!countedWait)
                            {
                                state.hadToWaitCount++;
                                countedWait = true;
                            }
                            if (log.isDebugEnabled())
                            {
                                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                            }
                            long wt = System.currentTimeMillis();
                            state.wait(poolTimeToWait);
                            state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        }
                        catch (InterruptedException e)
                        {
                            break;
                        }
                    }
                }
            }

            //如果获取PooledConnection成功,则更新其信息

            if (conn != null)
            {
                if (conn.isValid())
                {
                    if (!conn.getRealConnection().getAutoCommit())
                    {
                        conn.getRealConnection().rollback();
                    }
                    conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                    conn.setCheckoutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    state.activeConnections.add(conn);
                    state.requestCount++;
                    state.accumulatedRequestTime += System.currentTimeMillis() - t;
                }
                else
                {
                    if (log.isDebugEnabled())
                    {
                        log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                    }
                    state.badConnectionCount++;
                    localBadConnectionCount++;
                    conn = null;
                    if (localBadConnectionCount > (poolMaximumIdleConnections + 3))
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug("PooledDataSource: Could not get a good connection to the database.");
                        }
                        throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                    }
                }
            }
        }

    }

    if (conn == null)
    {
        if (log.isDebugEnabled())
        {
            log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }
        throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    }

    return conn;
}
class PooledConnection implements InvocationHandler {
    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class[]{Connection.class};
    private int hashCode = 0;
    private PooledDataSource dataSource;
    private Connection realConnection;
    private Connection proxyConnection;
    private long checkoutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
        this.proxyConnection = (Connection)Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        //当close时候,会回收 connection , 不会真正的close
        if("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) {
            this.dataSource.pushConnection(this);
            return null;
        } else {
            try {
                if(!Object.class.equals(method.getDeclaringClass())) {
                    this.checkConnection();
                }

                return method.invoke(this.realConnection, args);
            } catch (Throwable var6) {
                throw ExceptionUtil.unwrapThrowable(var6);
            }
        }
    }
}

总结

2 mybatis中的事务和隔离级别

2.1 jdbc中的事务

2.2 mybatis中的事务提交方式

  //用于在测试方法执行之前执行
    @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操作了

2.3 事务的回滚

 //用于在测试方法执行之前执行
    @BeforeEach
    public void init()throws Exception{
        // 设置为自动提交
        sqlSession = MybatisUtils.openSession();
        //获取dao的代理对象
        userMapper = sqlSession.getMapper(IUserMapper.class);
    }

    // 在测试结束之后执行
    @AfterEach
    public void destroy()throws Exception{
        //提交事务
        sqlSession.commit();
        //释放资源
        sqlSession.close();
    }

    @Test
    public void testRollback(){
        try{
            User condition = new User();
            condition.setId(1);
            condition.setNickname("尚云科技111299");
            int i = userMapper.update(condition);
            assertEquals(i,1);
            throw new Exception();
        }catch (Exception e){
            e.printStackTrace();
            // 进行数据回滚操作
            sqlSession.rollback();
        }
    }

2.4 事务的隔离级别

在我们学习数据库的时候,涉及到事务这一模块的时候经常会涉及到的三个名词就是

脏读 :
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问 这个数据,然后使用了这个数据。

不可重复读
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

幻读
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

事务隔离
这个时候我们就需要设置事务的隔离级别来解决这个问题

√: 可能出现 ×: 不会出现
在这里插入图片描述
mybatis中也可以设置隔离级别,只不过增加了一个没有事务的属性

package org.apache.ibatis.session;

public enum TransactionIsolationLevel {
    NONE(0),
    READ_COMMITTED(2),
    READ_UNCOMMITTED(1),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int level;

    private TransactionIsolationLevel(int level) {
        this.level = level;
    }

    public int getLevel() {
        return this.level;
    }
}

3 延迟加载策略

3.1 什么是延迟加载

延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

3.2 需求

需求:

3.3 association实现延迟加载

3.3.1 未实现延迟加载的时候

我们之前未实现延迟加载的时候,每次操作,都会直接查询出我们需要的数据
在这里插入图片描述
可以看到图中包含多条sql的执行

3.3.2 开启延迟加载

找到对应设置
https://mybatis.org/mybatis-3/zh/configuration.html#settings
在这里插入图片描述
通过lazyLoadingEnabled、aggressiveLazyLoading可以对延迟加载进行配置

  <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

添加了懒加载配置之后,我们在进行列表查询的过程中就不会再做大量的关联查询了,可以提升列表查询的效率,在我们用到具体字段之后才会进行关联查询。

3.3.3 aggressiveLazyLoading

属性开启之后,我们获取到变量的任意属性,就会触发懒加载,而关闭之后,我们只有触发到关联属性时,才会触发懒加载

3.3.4 配置每个关联字段的加载方式

在关联字段上也可以通过设置fetchType来指定加载方式。对于当个关联属性指定fetchType优先级是高于全局配置

    <resultMap id="addressResultMap" type="Address">
        <association property="user" column="user_id" javaType="User"
                     select="com.tledu.erp.mapper.IUserMapper.selectById" fetchType="lazy"/>
    </resultMap>

3.4 collection实现懒加载

collection的懒加载实现和association基本类似

4 使用注解进行开发

4.1 mybatis常用注解说明

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装

4.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);
}

4.3 使用Result进行映射

如何我们需要映射结果集的时候可以通过@Results注解进行映射

    @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);

4.4 注解进行关联查询

4.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);

4.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);

5 缓存

5.1 一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

5.1.1 测试

5.1.2 总结

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
在这里插入图片描述

5.2 二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
在这里插入图片描述

5.2.1 开启二级缓存

<setting name="cacheEnabled" value="true"/>

5.2.2 在映射文件中开启缓存支持

<mapper namespace="com.tledu.erp.mapper.IAddressMapper">
  <!-- 开启缓存支持-->
    <cache />
</mapper>

查询语句中需要指定useCache=“true”

<select id="selectOne" resultMap="addressResultMap" useCache="true">
        select *
        from t_address
        where id = #{id}
</select>

5.2.3 注意事项

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

@Data
public class Address implements Serializable {
    private Integer id;
    private String addr;
    private String phone;
    private String postcode;
    private Integer userId;
    /**
     * 创建用户,关联用户表
     */
    private User user;
}

5.2.4 总结

在使用二级缓存的时候,需要注意配置mybatis-config.xml中 开启二级缓存

<setting name="cacheEnabled" value="true"/>

6 分页

6.1为什么要分页

6.2怎么设计分页

基于这些属性设计分页的实体类

@Data
public class PageInfo<T> {
    /**
     * 每页有多少个
     */
    private int pageSize;
    /**
     * 当前是在第几页
     */
    private int currentPage;
    /**
     * 数据的总数
     */
    private int total;
    /**
     * 数据列表
     */
    private List<T> list;
    
    // 获取偏移量
    public int getOffset() {
        return (this.currentPage - 1) * this.pageSize;
    }
}

6.3实现分页功能

创建分页查询的方法

	/**
     * 分页查询
     *
     * @param user     查询条件
     * @param offset   起始位置
     * @param pageSize 每页容量
     * @return 用户列表
     */
    List<User> page(@Param("user") User user, @Param("offset") int offset, @Param("pageSize") int pageSize);

    /**
     * 统计总数
     *
     * @param user 查询条件
     * @return 总数
     */
    int count(@Param("user") User user);
    <select id="page" resultType="User">
        select *
        from t_user
        <where>
            <if test="user.nickname != null and user.nickname != ''">
                and nickname like concat('%',#{user.nickname},'%')
            </if>
            <if test="user.username != null and user.username != ''">
                and username = #{user.username}
            </if>
        </where>
        limit #{offset},#{pageSize};
    </select>
    <select id="count" resultType="int">
        select count(*)
        from t_user
        <where>
            <if test="user.nickname != null and user.nickname != ''">
                and nickname like concat('%',#{user.nickname},'%')
            </if>
            <if test="user.username != null and user.username != ''">
                and username = #{user.username}
            </if>
        </where>
    </select>

测试

  @Test
    public void page(){
        PageInfo<User> pageInfo = new PageInfo<User>();
        pageInfo.setCurrentPage(1);
        pageInfo.setPageSize(10);
        User user = new User();
        user.setNickname("天亮");
        // 加上筛选条件,根据nickname 或 username进行筛选
        List<User> list = userMapper.page(user,pageInfo.getOffset(),pageInfo.getPageSize());
        pageInfo.setList(list);
        pageInfo.setTotal(userMapper.count(user));
        System.out.println(pageInfo);
    }

6.4分页插件

https://pagehelper.github.io/

  1. 引入依赖
 <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
  1. 配置拦截器
    在mybatis的配置文件中增加插件
<!--
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?,
    typeAliases?, typeHandlers?,
    objectFactory?,objectWrapperFactory?,
    plugins?,
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>
  1. 配置插件
    https://pagehelper.github.io/docs/howtouse/#2-%E9%85%8D%E7%BD%AE%E6%8B%A6%E6%88%AA%E5%99%A8%E6%8F%92%E4%BB%B6
  2. 使用插件
    @Test
    public void testList() throws IOException {
        SqlSession session = MybatisUtils.openSession();
        User condition = new User();
        // 插件里提供的分页工具,在要查询之前,执行一下PageHelper.startPage(当前页数,每页的容量), 当使用工具时候,会导致懒加载失败
        // 加了这个操作,插件就会在sql语句中拼接limit限制,并且还会统计总个数
        PageHelper.startPage(1,5);
        List<User> users = session.getMapper(IUserMapper.class).list(condition);
        // 拿到结果之后通过PageInfo.of() 的方法,获得pageInfo
        com.github.pagehelper.PageInfo<User> list = com.github.pagehelper.PageInfo.of(users);
        System.out.println(users);
    }

标签:总结,分页,PooledConnection,private,查询,Connection,Mybatis,id,连接池
来源: https://blog.csdn.net/m0_46552030/article/details/123642764