前言(分页的思想)
首先简单分析一下,我们第一次发送请求肯定是页面初始化的时候这时候页数和显示的条目都是默认的,查询条件也是查询全部。第二次按照“圣墟”进行查询的时候,只有sql语句发生了变化。第三次按照“圣墟”进行查询但是这一次我在查询过后点击下一页,这时候多了两个参数,关键词和页码。
以上可知,分页只改变了sql语句和页码值,其他查询条件不变,所以我们只需要在页码和sql语句做文章即可。(这只是基本思想,可能还有类别等等参数)
一、创建数据库连接以及实体对象
我们操作的数据肯定是动态的,这里博主用的数据库是MySQL。
创建数据库帮助类(用于连接数据库)
package com.xw.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * 提供了一组获得或关闭数据库对象的方法 * */ public class DBAccess { private static String driver; private static String url; private static String user; private static String password; static {// 静态块执行一次,加载 驱动一次 try { InputStream is = DBAccess.class .getResourceAsStream("config.properties"); Properties properties = new Properties(); properties.load(is); driver = properties.getProperty("driver"); url = properties.getProperty("url"); user = properties.getProperty("user"); password = properties.getProperty("pwd"); Class.forName(driver); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 获得数据连接对象 * * @return */ public static Connection getConnection() { try { Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } public static void close(ResultSet rs) { if (null != rs) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } public static void close(Statement stmt) { if (null != stmt) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } public static void close(Connection conn) { if (null != conn) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } public static void close(Connection conn, Statement stmt, ResultSet rs) { close(rs); close(stmt); close(conn); } public static boolean isOracle() { return "oracle.jdbc.driver.OracleDriver".equals(driver); } public static boolean isSQLServer() { return "com.microsoft.sqlserver.jdbc.SQLServerDriver".equals(driver); } public static boolean isMysql() { return "com.mysql.cj.jdbc.Driver".equals(driver); } public static void main(String[] args) { Connection conn = DBAccess.getConnection(); System.out.println(conn); DBAccess.close(conn); System.out.println("isOracle:" + isOracle()); System.out.println("isSQLServer:" + isSQLServer()); System.out.println("isMysql:" + isMysql()); System.out.println("数据库连接(关闭)成功"); } }
我这里并没有直接将我们的数据库账户以及密码填写在数据库帮助类中,原因有以下几点
- 安全性:通过将账户密码存储在配置文件中,可以将敏感信息与代码分离开来,避免直接将敏感数据硬编码在源代码中。这有助于减少泄露敏感信息的风险,特别是在团队环境中,可以限制敏感信息的可见性和访问权限。
- 灵活性:将账户密码存储在配置文件中可以提供更大的灵活性。通过修改配置文件,可以在不修改源代码的情况下更改数据库的连接信息,包括账户、密码、主机地址和其他相关配置。这样可以方便地进行配置管理和部署,而不必重新编译或修改代码。
- 维护性:将账户密码保存在配置文件中可以提高代码的可维护性。如果需要更改数据库连接信息,只需修改配置文件而不必修改源代码。这样可以减少潜在的错误和冲突,并且更易于管理和维护。
- 可配置性:配置文件具有可读性和易于修改的特点,允许用户根据特定需求对账户密码进行自定义配置。这样可以根据不同的环境或部署需求,为不同的数据库设置不同的账户密码,而无需修改源代码并重新编译。
配置文件(用于访问数据库)
#oracle9i #driver=oracle.jdbc.driver.OracleDriver #url=jdbc:oracle:thin:@localhost:1521:orcl #user=scott #pwd=****** #sql2005 #driver=com.microsoft.sqlserver.jdbc.SQLServerDriver #url=jdbc:sqlserver://localhost:1433;DatabaseName=test1 #user=sa #pwd=****** #sql2000 #driver=com.microsoft.jdbc.sqlserver.SQLServerDriver #url=jdbc:microsoft:sqlserver://localhost:1433;databaseName=unit6DB #user=sa #pwd=****** #mysql driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis_ssm?useUnicode=true&characterEncoding=UTF-8&useSSL=false user=root pwd=******
填写自己的数据库url路径以及账户密码即可
我们在数据库帮助类测试一下调用getConnection方法,查看是否连接成功
出现第一行代码说明连接成功 ,可以继续我们接下来的操作
编写实体类(Book与数据库表一致)
package com.xw.entity; /**book实体类 * @author Java方文山 * */ public class Book { private int bid; private String bname; private float price; public Book() { // TODO Auto-generated constructor stub } public Book(int bid, String bname, float price) { super(); this.bid = bid; this.bname = bname; this.price = price; } @Override public String toString() { return "Book [bid=" + bid + ", bname=" + bname + ", price=" + price + "]"; } public int getBid() { return bid; } public void setBid(int bid) { this.bid = bid; } public String getBname() { return bname; } public void setBname(String bname) { this.bname = bname; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } }
二、创建pagebean分页工具类以及优化Dao层
这里我将页码、条目、总记录数、url请求路径、上一次请求携带的参数、以及是否分页,六个字段作为PageBean分页工具类的属性,方便我们使用以及制作自定义标签,并且初始化PageBean分页工具类的值(值从request获取),提供计算起始记录的下标、获取最大页数、以及上一页下一页的方法。
注意:看过我分享的通用分页博客的话,可不要误导了哦,这个分页工具类是优化版主要是为了更好的实现自定义分页标签。
PageBean分页工具类
package com.xw.util; import java.util.Map; import javax.servlet.http.HttpServletRequest; /**分页工具类 * @author Java方文山 * */ public class PageBean { private int page = 1;// 页码 private int rows = 10;// 页大小 private int total = 0;// 总记录数 private boolean pagination = true;// 是否分页 private String url;// 保存上一次的请求路径 private Map<String, String[]> parameterMap;// 保存上一次请求路径携带的参数 // 初始化数据获取request获取的值并赋值给Pagebean工具类 public void setPagebean(HttpServletRequest req) { // 初始化当前页码page this.setPage(req.getParameter("page") != null ? Integer.valueOf(req.getParameter("page")) : 1); // 初始化展示页大小total this.setRows(req.getParameter("rows") != null ? Integer.valueOf(req.getParameter("rows")) : 10); // 初始化是否分页pagination // 只有填写了false字符串,才代表不分页所有才需要这样做 this.setPagination(req.getParameter("pagination") != null ? !"false".equals(pagination) : true); // 保存上一次的查询请求url this.setUrl(req.getRequestURI().toString()); // 保存上一次查询的条件parameterMap this.setParameterMap(req.getParameterMap()); } public PageBean() { super(); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Map<String, String[]> getParameterMap() { return parameterMap; } public void setParameterMap(Map<String, String[]> parameterMap) { this.parameterMap = parameterMap; } public int getPage() { return page; } public void setPage(int page) { this.page = page; } public int getRows() { return rows; } public void setRows(int rows) { this.rows = rows; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; } public void setTotal(String total) { this.total = Integer.parseInt(total); } public boolean isPagination() { return pagination; } public void setPagination(boolean pagination) { this.pagination = pagination; } /** * 获得起始记录的下标 * * @return */ public int getStartIndex() { return (this.page - 1) * this.rows; } /** * @return 获取最大页数 */ public int getMaxPage() { return this.total % this.rows == 0 ? this.total / this.rows : (this.total / this.rows) + 1; } /** * 上一页 * * @return 页码 */ public int getPrevPage() { // 如果当前页数大于1就允许-1如果当前页数小于1那么就还是当前页数 return this.page > 1 ? this.page - 1 : this.page; } /** * 下一页 * * @return 页码 */ public int getNextPage() { // 如果当前页数小于最大页数就允许+1如果当前页数+1后大于最大页数那么就还是当前页数 return this.page < this.getMaxPage() ? this.page + 1 : this.page; } @Override public String toString() { return "PageBean [page=" + page + ", rows=" + rows + ", total=" + total + ", pagination=" + pagination + "]"; } }
因为刚刚分析了每次只有sql语句与页码不同,所以我们将Dao层进行优化,这里就不细讲了,直接上代码,感兴趣的可以查看我写的通用分页。
编写一个BaseDao<T>存放通用的分页方法
package com.xw.dao; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.xw.entity.Book; import com.xw.util.DBAccess; import com.xw.util.PageBean; import com.xw.util.StringUtils; /**封装通用分页 * @author Java方文山 * */ public class BaseDao<T> { /**封装通用的分页方法 * @param cl 需要分页的实体 * @param sql sql语句 * @param p 封装的分页工具类 * @return 按照条件查询的结果 */ public List<T> executeQuery(Class cl,String sql,PageBean p) throws Exception{ //获取连接对象 Connection conn=null;//连接对象 PreparedStatement ps=null;//执行对象 ResultSet rs=null;//结果集对象 List<T> list=new ArrayList<T>();//集合 //判断是否分页 if(p!=null && p.isPagination()) { //总记录数 String count=getSQLcount(sql); conn=DBAccess.getConnection(); ps=conn.prepareStatement(count); rs=ps.executeQuery(); //将总记录数赋值给分页工具类 if(rs.next()) { p.setTotal(rs.getObject("n").toString()); } //初始下标(第几条开始) String pagesize=getSQpagesize(sql,p); conn=DBAccess.getConnection(); ps=conn.prepareStatement(pagesize); rs=ps.executeQuery(); }else { //加载驱动 conn=DBAccess.getConnection(); //执行sql语句 ps=conn.prepareStatement(sql); //返回结果集 rs=ps.executeQuery(); } try { //遍历结果集 while(rs.next()) { //创建类的实例 T T=(T) cl.newInstance(); //拿到传递进来的实体所有属性 Field[] declaredFields = cl.getDeclaredFields(); //遍历属性 for (Field field : declaredFields) { //打开访问权限 field.setAccessible(true); //等价于b.setBid(rs.getInt("bid"),设置T实例的属性 field.set(T, rs.getObject(field.getName())); } //将实体添加到集合 list.add(T); } } catch (Exception e) { //捕捉异常 e.printStackTrace(); }finally { //关闭资源 DBAccess.close(conn); DBAccess.close(rs); DBAccess.close(ps); } //返回结果 return list; } /**计算最终展示数据的sql * @param sql 传递过来的sql语句 * @param p 分页工具类 * @return */ private String getSQpagesize(String sql, PageBean p) { return sql+" LIMIT "+p.getStartIndex()+","+p.getRows(); } /**查看总记录数 * @param sql 传递过来的sql语句 * @return 数量 */ private String getSQLcount(String sql) { return "SELECT COUNT(1) as n from("+sql+") t"; } }
BookDao继承BaseDao<T>
package com.xw.dao; import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import org.junit.Test; import com.xw.entity.Book; import com.xw.util.DBAccess; import com.xw.util.PageBean; import com.xw.util.StringUtils; /** * book的dao类 * * @author Java方文山 * */ public class BookDao extends BaseDao<Book> { public List<Book> BaseDaoTest(Book b, PageBean p) throws Exception { // 编写sql语句 String sql = "SELECT * FROM t_mvc_book where 1=1 "; // 当关键词不为空的时候,拼接模糊查询的sql语句 String bname = b.getBname(); if (StringUtils.isNotBlank(bname)) { sql += " and bname like '%" + bname + "%'"; } //调用basedao封装的方法 return executeQuery(b.getClass(), sql, p); } }
看到这里是不是非常的吃惊,怎么会这么简短,将来无论是什么StudentDao、GoodsDao继承BaseDao<T>都是这么几行代码,大大的提升了我们的效率。
这里我们的准备工作就做完了,下面进行我们的重头戏——自定义分页标签