mybatis连接池,事务,隔离级别,迟加载,注解开发,缓存,分页

一.连接池

1.介绍

mybatis使用的是自己的连接池技术,可以在mybatis-config.xml中进行配置
如图:
在这里插入图片描述

2.mybaits连接池的分类

  • UNPOOLED 不使用连接池的数据源
  • POOLED 使用连接池的数据源
  • JNDI 使用JNDI实现的数据库连接池

3.UNPOOLED分析

UNPOOLED 不使用连接池的数据源,当 dateSource 的type属性被配置成了UNPOOLED,MyBatis 首先会实例化一个UnpooledDataSourceFactory工厂实例,然后通过.getDataSource() 方法返回一个UnpooledDataSource 实例对象引用,我们假定为dataSource。
使用 UnpooledDataSource 的 getConnection() ,每调用一次就会产生一个新的 Connection 实例对象。UnPooledDataSource 的 getConnection() 方法实现如下:

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会做以下事情:

  • 初始化驱动: 判断driver驱动是否已经加载到内存中,如果还没有加载,则会动态地加载driver类,并实例化一个Driver对象,使用DriverManager.registerDriver()方法将其注册到内存中,以供后续使用。
  • 创建Connection对象: 使用DriverManager.getConnection()方法创建连接。
  • 配置Connection对象: 设置是否自动提交autoCommit和隔离级别isolationLevel。
  • 返回Connection对象

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

4.POOLED分析

PooledDataSource: 将java.sql.Connection对象包裹成PooledConnection对象放到了PoolState类型的容器中维护。 MyBatis将连接池中的PooledConnection分为两种状态: 空闲状态(idle)和活动状态(active),这两种状态的PooledConnection对象分别被存储到PoolState容器内的idleConnections和activeConnections两个List集合中:
idleConnections:
空闲(idle)状态PooledConnection对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从此集合中PooledConnection对象。当用完一个java.sql.Connection对象时,MyBatis会将其包裹成PooledConnection对象放到此集合中。
**activeConnections: **
活动(active)状态的PooledConnection对象被放置到名为activeConnections的ArrayList中,表示当前正在被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从idleConnections集合中取PooledConnection对象,如果没有,则看此集合是否已满,如果未满,PooledDataSource会创建出一个PooledConnection,添加到此集合中,并返回
现在让我们看一下popConnection()方法到底做了什么:

  • 先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。
  • 查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;
  • 看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。
  • 线程等待,循环2步
/*
 * 传递一个用户名和密码,从连接池中返回可用的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;
}

当我们的程序中使用完Connection对象时,如果不使用数据库连接池,我们一般会调用 connection.close() 方法,关闭connection连接,释放资源
调用过close()方法的Connection对象所持有的资源会被全部释放掉,Connection对象也就不能再使用。那么,如果我们使用了连接池,我们在用完了Connection对象时,需要将它放在连接池中,该怎样做呢?
可能大家第一个在脑海里闪现出来的想法就是:我在应该调用con.close()方法的时候,不调用close()方法,将其换成将Connection对象放到连接池容器中的代码!
怎样实现Connection对象调用了close()方法,而实际是将其添加到连接池中
这是要使用代理模式,为真正的Connection对象创建一个代理对象,代理对象所有的方法都是调用相应的真正Connection对象的方法实现。当代理对象执行close()方法时,要特殊处理,不调用真正Connection对象的close()方法,而是将Connection对象添加到连接池中。
MyBatis的PooledDataSource的PoolState内部维护的对象是PooledConnection类型的对象,而PooledConnection则是对真正的数据库连接java.sql.Connection实例对象的包裹器。
PooledConnection对象内持有一个真正的数据库连接java.sql.Connection实例对象和一个java.sql.Connection的代理:

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

过程图:
在这里插入图片描述

二.事务与隔离级别

1.事务的提交方式

在jdbc中,事务的提交方式是通过setAutoCommit()接口来进行设置的,在mybatis中对jdbc中的事务提交方式进行了封装,它的提交方式如下

// 设置为自动提交
sqlSession = MybatisUtils.openSession(true);
//默认不写参数的话是手动提交
//手动提交
sqlSession.commit();

2.事务回滚

在执行一个多条sql语句的时候,如果中间发生异常,那么就需要回滚,mybatis中回滚的方式
sqlSession.rollback();

    //测试报错后事务的回滚
    @Test
    public void TestRollBack(){
        try {
            //插入用户
            User user = new User();
            user.setNickname("张三");
            userMapper.insertUser(user);
            //模拟中途报错
            int a = 10/0;
            //模拟插入对应的用户地址
            Address address = new Address();
            address.setAddress("方大科技园");
            address.setUserId(user.getId());
            addressMapper.insertAddress(address);
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();//报错回滚
        }
    }

3.事务的隔离级别

1.脏读,不可重复读,幻读

脏读:读取未提交的数据,这个很好理解,如果读取到了未提交的数据,若该数据被回滚了,那么便读到了脏数据(不应该存在的数据)

不可重复读:两次读到的数据不一致,在一次事务的过程中,第一次读到了数据A,在执行该事务的过程中,该数据被修改了,等再次需要读这个数据的时候,数据已经发生了变化,就会发生一些错误

幻读:开始没有这条数据,但是后面再读就有了,这个一般设计到插入操作,开始读的时候为空,但是在事务的进行过程中,该条目被插入了,再次读的时候,该数据就有了,这也会发生一些错误

用最简答的几个字总结一下:
脏读:读未提交
不可重复读:读的前后不一致(修改操作)
幻读:数据无中生有(插入操作)

2.事务的隔离级别

事务的隔离级别就是解决以上问题的方法:
√: 可能出现 ×: 不会出现

脏读不可重复读幻读说明
Read uncommitted直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别 的事务可以读到这个改变.这是很不安全的。允许任务读取数据库中未提交的数据更改,也称为脏读。
Read committed×直译就是"读提交",可防止脏读,意思就是语句提交以后即执行了COMMIT以后,别的事务才能读到这个改变. 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别
Repeatable read××直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的.在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
Serializable×××直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行. 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

3.mybatis中设置隔离级别

//读提交,解决脏读(只读提交的数据)  
session = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);
//可重复读,解决不可重复读(对数据进行加锁)
session = sqlSessionFactory.openSession(TransactionIsolationLevel.REPEATABLE_READ);
//序列化,解决幻读,完全串行化的读,效率较低
session = sqlSessionFactory.openSession(TransactionIsolationLevel.SERIALIZABLE);
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;
    }
}

三.延迟加载策略

在使用到关联查询的时候,需要数据的时候才会进行加载,那么这就需要进行懒加载

1.全局配置

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

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

2.局部配置

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

    <resultMap id="lazyLoadTestMap" type="Address" autoMapping="true">
        <result column="user_id" property="userId"></result>
        <association property="user" column="user_id" javaType="User" fetchType="lazy" select="com.tl.mybatis03.mapper.UserMapper.findById">
        </association>
    </resultMap>
    <select id="lazyLoadTest" parameterType="int" resultMap="lazyLoadTestMap">
        select * from address where id = #{id}
    </select>

四.使用注解进行开发

1.mybatis常用注解说明

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

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

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.注解进行关联查询

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

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

五.缓存

1.介绍

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存
![image.png](https://img-blog.csdnimg.cn/img_convert/b5a2260ce683590ded62897dc7e2b1bd.png#height=217&id=Ph5Un&margin=[object Object]&name=image.png&originHeight=217&originWidth=594&originalType=binary&ratio=1&size=24633&status=done&style=none&width=594)

2.一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
测试:
在这里插入图片描述

清除一级缓存的方式:​

session.clearCache();

3.二级缓存

二级缓存保存在mapper中,可以在过个sqlSession中共享,需要进行配置

配置
开启二级缓存

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

配置mapper支持缓存

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

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

4.总结

  • 在使用二级缓存的时候,需要注意配置mybatis-config.xml中 开启二级缓存
  • 然后再mapper映射文件中使用catch标签标注开启,并对需要换成的语句添加useCache=”true”
    • 在mapper的映射文件中使用,代表当前mapper是开启二级缓存的
    • 在需要二级缓存的查询上增加useCache = true,代表当前查询是需要缓存的
  • 并且对应封装数据的实体类需要实现Serializable 接口
    • 对待缓存的数据,实现Serialization接口,代表这个数据是可序列化
  • 只有当sqlSession close之后,二级缓存才能生效
  • 当执行增删改操作的时候,必须执行commit()才能持久化到数据库中,同时二级缓存清空
  • session.clearCache()无法清除二级缓存,如果需要清除二级缓存,可以通过sqlSessionFactory.getConfiguration().getCache(“缓存id”).clear();
  • 但是当我们查询语句中,执行commit() 或者是close()关闭session,都不会清空二级缓存

六.分页

一、为什么要分页

数据特别多的时候,单次请求返回大量的数据接口会非常慢。
对于数据量特别大的查询,我们都会采用分页查询

二、怎么设计分页

  • 每页有多少个
  • 当前是在第几页
  • 数据的总数
  • 数据列表

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

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

三、实现分页功能

创建分页查询的方法

	/**
     * 分页查询
     *
     * @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);
    }

四、分页插件

https://pagehelper.github.io/

1. 引入依赖

 <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>

2. 配置拦截器

在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>

3. 配置插件

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

4. 使用插件

    @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);
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Antgeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值