2019独角兽企业重金招聘Python工程师标准>>>
1、什么是分页?
分页,是一种将所有数据分段展示给用户的技术.用户每次看到的不是全部数据,其中的一部分,如果在其中没有找到自习自己想要的内容,用户可以通过制定页码或是翻页的方式转换可见内容,直到找到自己想要的内容为止.其实这和我们阅读书籍很类似.
下面显示了网络常用的几种的分页方式:
2、分页的意义
分页确实有效,但它一定会加大系统的复杂度,但可否不分页呢?如果数据量少的话当然可以.但是对于企业信息系统来说数据量不会限制在一个小范围内.如果不顾一切的Select * from某个表,再将返回的数据一古脑的扔给客户,即使客户能够忍受成千上万足够让人眼花缭乱的表格式数据,繁忙的网络,紧张的服务器也会提出它们无声的抗议,甚至有时会以彻底的罢工作为终结.这个结局有点像古代为所欲为的暴君和他忍无可忍的臣民之间的故事.
程序员不是暴君,他希望程序使生活变得更好而不是更糟.考虑到企业信息系统多是三层甚至更多层架构的事实,程序员在向客户展示数据时都应该采取分页的形式.如果他不想被抱怨淹没或是半夜被电话惊醒的话.
3、在什么地方进行分页处理(数据库进行分页处理)
在进行分页之前,让我们回想一下.在典型的三层架构中,从请求到返回数据到客户端的过程.如下图所示:
从上面的图中我们可以观察得知,在SQL语句处理完毕后,数据库,WebApplication和Browser都能进行分页,那在哪里分页好呢? 判断的标准是速度,显而易见,数据库服务器,Web应用服务器和客户端之间是网络,如果网络传递的数据量越少,则客户端获得响应的速度越快.而且一般来说,数据库服务器和Web应用服务器的处理能力一般比客户端要强很多.从这两点来看,在客户端分页的方案是最不可取的. 其次就剩下了在Web服务器端分页和在数据库端分页两种方式了,如果选择在Web服务器端分页的话,大部分的被过滤掉的数据还是被传输到了Web应用服务器端,与其这样还不如直接在数据库端进行分页. 因此比较好的分页做法应该是每次翻页的时候只从数据库里检索页面大小的块区的数据。这样虽然每次翻页都需要查询数据库,但查询出的记录数很少,网络传输数据量不大,如果使用连接池更可以略过最耗时的建立数据库连接过程。而在数据库端有各种成熟的优化技术用于提高查询速度,比在应用服务器层做缓存有效多了。 |
4、分页的SQL语句
如果我们使用JDBC访问数据库服务器的数据的话,那么不同的类型数据库的的采用的分页SQL语句(方言)是不一样滴. 1.MySQL DataBase 查询MySQL5的文档中找到这一段内容:LIMIT子句可以被用于限制被SELECT语句返回的行数.LIMIT取一个或两个数字自变量,自变量必须是非负的整数常数(当使用已预备的语句时除外),使用两个自变量时,第一个自变量指定返回的第一行的偏移量,第二个自变量指定返回的行数的最大值。初始行的偏移量为0(不是1):
mysql> SELECT * FROM tbl LIMIT 5,10;  # Retrieve rows 6-15  偏移量为5,而第一行的偏移量为0,所以从第六行记录开始,取10条. | 对于这一SQL语句自己的理解是:Limit后的两个参数中,参数1是起始下标,它从0开始,开始行记录就是(参数1)+1;参数2是返回的记录数。 |
2.Oraclie DataBase 在Oracle数据库中,分页方式没有MySql这样简单,它需要依靠rownum来实现.Rownum表示一条记录的行号,值得注意的是它在获取每一行后才赋予.因此,想指定rownum的区间来取得分页数据在一层查询语句中是无法做到的,要分页还要进行一次查询.下图是分页的具体示例,它从帐户表中返回前十条记录. 这是由于CBO优化模式下,Oracle可以将外层的查询条件推到内层查询中,以提高内层查询的执行效率。对于第一个查询语句,第二层的查询条件WHERE ROWNUM <= 40就可以被Oracle推入到内层查询中,这样Oracle查询的结果一旦超过了ROWNUM限制条件,就终止查询将结果返回了。 |
5、MySQL+Java代码实现分页
像如平时我们很在分页中看到的,分页的时候返回的不仅包括查询的结果集(List),而且还包括总的页数(totalPageNum)、当前第几页(currentPageNum)等等信息,所以我们封装一个查询结果Page类,代码如下:其中使用泛型是为了能使的该分页类能进行重用,比如在查询用户时可以封装User对象、在查询财务中的流向单时可以封装流向单FlowCard类. |
package com.itheima.web.formbean;import java.io.Serializable;import java.util.List;/** * 分页模板类 * @author LISAI */public class Page<T> implements Serializable { private static final long serialVersionUID = 1L; /*每个页面数据集合*/ private List<T> totalRecords; /*每个页面最多记录条数*/ private int pageMaxSize =10; //开发时,通过web.xml文件参数配置. /*当前页码*/ private int currentPageNum ; /*总共多少页,总页数*/ private int totalPageNum ; /*总记录数*/ private int totalRecordsNum ; /*每页的开始索引数*/ private int startIndex ; public Page( int currentPageNum,int totalRecordsNum){ //获取当前页码 this.currentPageNum = currentPageNum; //获取记录 this.totalRecordsNum = totalRecordsNum; //计算总页数 this.totalPageNum = totalRecordsNum%pageMaxSize ==0?totalRecordsNum/pageMaxSize:totalRecordsNum/pageMaxSize+1; if(this.currentPageNum >this.totalPageNum){ this.currentPageNum = this.totalPageNum; } //计算每页的开始索引数 this.setStartIndex((this.currentPageNum-1)*pageMaxSize); //判断总页数<=9 if(this.totalPageNum <=9){ this.startPageNum = 1; this.endPageNum = this.totalPageNum; } else{ this.startPageNum = this.currentPageNum-4; this.endPageNum = this.currentPageNum+4; if(this .startPageNum <1){ this.startPageNum = 1; this.endPageNum = this.startPageNum+8; } if(this .endPageNum >this.totalPageNum){ this.endPageNum = this.totalPageNum; this.startPageNum = this.totalPageNum -8; } } } /*上一页*/ @SuppressWarnings( "unused") private int proviousPageNum ; /*下一页*/ @SuppressWarnings( "unused") private int nextPageNum ; /*开始页码*/ private int startPageNum ; /*结束页码*/ private int endPageNum ; public int getProviousPageNum() { //如果当前页码-1大于0时,表示存在前一页.如果小于0,则直接赋值为首页. return proviousPageNum=(this .currentPageNum -1>0?this.currentPageNum-1:1); } public void setProviousPageNum(int proviousPageNum) { this.proviousPageNum = proviousPageNum; } public int getNextPageNum() { //如果当前页码+1大于总页数,则直接为尾页.如果不是,表示存在后一页. return nextPageNum=(this .currentPageNum +1>totalPageNum ?totalPageNum :this.currentPageNum+1); } public void setNextPageNum(int nextPageNum) { this.nextPageNum = nextPageNum; } public List<T> getTotalRecords() { return totalRecords ; } public void setTotalRecords(List<T> totalRecords) { this.totalRecords = totalRecords; } public int getPageMaxSize() { return pageMaxSize ; } public void setPageMaxSize(int pageMaxSize) { this.pageMaxSize = pageMaxSize; } public int getCurrentPageNum() { return currentPageNum ; } public void setCurrentPageNum(int currentPageNum) { this.currentPageNum = currentPageNum; } public int getTotalPageNum() { return totalPageNum ; } public void setTotalPageNum(int totalPageNum) { this.totalPageNum = totalPageNum; } public long getTotalRecordsNum() { return totalRecordsNum ; } public int getStartIndex() { return startIndex ; } public void setStartIndex(int startIndex) { this.startIndex = startIndex; } public void setTotalRecordsNum(int totalRecordsNum) { this.totalRecordsNum = totalRecordsNum; } public int getStartPageNum() { return startPageNum ; } public void setStartPageNum(int startPageNum) { this.startPageNum = startPageNum; } public int getEndPageNum() { return endPageNum ; } public void setEndPageNum(int endPageNum) { this.endPageNum = endPageNum; }} |
以查询用户为例,用户选择查询条件.首先调用Servlet获取查询参数,然后请求业务逻辑层取得分页封装结果类。业务逻辑调用Dao层取得结果集、取得中记录数封装成分页类。最后Servlet将结果设置到JSP页面显示. |
在service(request,response)方法中调用下面这个方法. // 查询分页数据 private void findAllCustomer(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { String currentPageNum = request.getParameter("currentPageNum" ); Page<Customer> page=customerService.findPageRecords(currentPageNum); request.setAttribute( "page", page); request.getRequestDispatcher( "/jsps/listCustomers.jsp").forward(request, response); } catch (Exception e) { e.printStackTrace(); } } |
接着是业务逻辑层Service代码,代码如下: |
/** * 查询所有客户信息,并且分页显示 * @param currentPageNum * @return Page 封装分页信息的对象 */ public Page<Customer> findPageRecords(String currentPageNum) { int totalRecordsNum = customerDao.getTotalRecords(); int pageNum = 1; if(currentPageNum!=null&&!currentPageNum.trim().equals( "")){ try { pageNum = Integer.parseInt(currentPageNum); } catch (NumberFormatException e) { throw new RuntimeException("SB,页码必须是整数"); } } Page<Customer> page = new Page<Customer>(pageNum, totalRecordsNum); List<Customer> totalRecords = customerDao.findPageRecords(page.getStartIndex(), page.getPageMaxSize()); page.setTotalRecords(totalRecords); return page; } |
//关于数据库的操作无非是SQL语句和参数的不同,所以下面就直接写SQL语句代替实现. /** * 查询分页数据 * @param startIndex 开始记录的索引 * @param pageSize 一次查询的条数 * @return List<Customer> */ public List<Customer> findPageRecords( long startIndex, int pageSize){ String sql = "select * from tb_customer limit ?,?"; } /** * 获取数据库有关客户信息总记录数 * @return int */ public int getTotalRecords(){ String sql = "select count(*) from tb_customer"; } |
6、无刷新的AJAX分页技术
代码中每次通过servlet请求取得结果集后,都会转向到一个jsp页面显示结果,这样每次查询页面都会刷新一下,比如查询出现结果集后要查看第三页,页面就会刷新一下。这样页面给人的效果感觉就会有点不舒服,所以我们希望能够在通过条件查询结果集后无论访问哪一页,页面都不会刷新,而只是结果集变化。
要解决这个,我想大家很容易就会想到Ajax.
是啊,这就要请Ajax出山了。当通过查询条件查询到结果集后,以后每次访问任何一页都通过Ajax来访问,使用异步Ajax与Servlet进行交互,将结果查询出来返回给Ajax,这样页面内容因为Ajax返回结果而改变,而页面却不会刷新,这就实现了无刷新的分页技术.
以后使用JQuery+JQuery.Pagination框架实现.
参考博客及实现:
http://blog.csdn.net/xiaoyousifang/article/details/5824802
7、MySQL中处理大文本
Text类型和Blob类型(二进制)
package com.ithiema.jdbc.lob;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.FileReader;import java.io.FileWriter;import java.io.InputStream;import java.io.OutputStream;import java.io.Reader;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import org.junit.Test;import com.itheima.utils.JdbcUtil;public class LobDemo1 { @Testpublic void testWrtieLob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("insert into t1(id,content) values(?,?)"); stmt.setInt(1, 1); File file = new File("src/a.txt"); FileReader reader = new FileReader(file); stmt.setCharacterStream(2, reader, (int)file.length()); stmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } } @Testpublic void testReadLob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("select id,content from t1"); rs=stmt.executeQuery();while(rs.next()){ System.out.println(rs.getInt("id")); FileWriter writer = new FileWriter("d:/a.txt"); Reader reader=rs.getCharacterStream("content");int len = -1;char[] ch = new char[1024];// String str=null;while((len=reader.read(ch))!=-1){// str+=new String(ch); writer.write(ch, 0, len); } reader.close();// System.out.println(str); writer.close(); } } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } } @Testpublic void testWrtieBlob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("select id,content from t2"); rs=stmt.executeQuery();while(rs.next()){ OutputStream out = new FileOutputStream("d:/1.bmp"); InputStream in = rs.getBinaryStream("content");int len = -1;byte[] buff = new byte[1024];while((len=in.read(buff))!=-1){ out.write(buff); } in.close(); out.close(); } } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } } @Testpublic void testReadBlob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("insert into t2(id,content) values(?,?)"); stmt.setInt(1, 1); InputStream in = new FileInputStream("src//1.bmp"); stmt.setBinaryStream(2,in,in.available()); stmt.executeUpdate(); in.close(); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } }} |
8、MySQL中批处理
业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。 实现批处理有两种方式:
package com.ithiema.jdbc.batch;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.Statement;import org.junit.Test;import com.itheima.utils.JdbcUtil;public class BatchDemo1 { @Testpublic void testBatch(){ Connection conn = null; Statement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.createStatement(); String sql1 = "insert into t3(id,name) values(1,'aaa')"; String sql2 = "insert into t3(id,name) values(2,'bbb')"; String sql3 = "insert into t3(id,name) values(3,'ccc')"; String sql4 = "delete from t3 where id=1"; stmt.addBatch(sql1); stmt.addBatch(sql2); stmt.addBatch(sql3); stmt.addBatch(sql4); stmt.executeBatch(); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } } @Testpublic void testBatch1(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); String sql = "insert into t3(id,name) values(?,?)"; stmt = conn.prepareStatement(sql);for(int i=1;i<6;i++){ stmt.setInt(1, i); stmt.setString(2, "itheima"+i); stmt.addBatch(); } stmt.executeBatch(); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } } @Testpublic void testBatch2(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); String sql = "insert into t3(id,name) values(?,?)"; stmt = conn.prepareStatement(sql);for(int i=0;i<10000;i++){ stmt.setInt(1, i+1); stmt.setString(2, "itheima"+i+1); stmt.addBatch();if(i%1000==0){ stmt.executeBatch(); stmt.clearBatch(); } } stmt.executeBatch(); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } }} |
9、MySQL存储过程(翻阅MySQL文档)
package com.ithiema.jdbc.procedure;import java.io.File;import java.io.FileReader;import java.sql.CallableStatement;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.Types;import org.junit.Test;import com.itheima.utils.JdbcUtil;public class ProcedureDemo { @Testpublic void testProcedure(){ Connection conn = null; CallableStatement stmt = null; ResultSet rs = null;try { conn = JdbcUtil.getConnection(); stmt = conn.prepareCall("{call demoSp(?,?)}");//输入参数,设置值 stmt.setString(1, "itheima");//输出参数要注册类型 stmt.registerOutParameter(2, Types.VARCHAR);//执行存储过程 stmt.execute();//获取输出参数的值 String value = stmt.getString(2); System.out.println(value); } catch (Exception e) { e.printStackTrace(); } finally{ JdbcUtil.release(conn, stmt, rs); } }} |
10、MySQL数据库的事务(重点)
1.事务的概念 事务指逻辑上的一组操作,例如一组SQL语句.要么一起全部执行成功,要么全部执行失败.本地事务和分布式事务的区别:List协议分析.JTA相关知识点. |
2.MySQL数据库对于事务的SQL语句 START TRANSACTION | BEGIN [WORK] COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET AUTOCOMMIT = {0 | 1}
START TRANSACTION或BEGIN语句可以开始一项新的事务 COMMIT可以提交当前事务,是变更成为永久变更 ROLLBACK可以 回滚当前事务,取消其变更 SET AUTOCOMMIT语句可以禁用或启用默认的autocommit模式,用于当前连接. 注意:MySQL数据库默认是自动提交模式即"一句SQL执行",就直接提交. |
3.在JDBC中使用事务(超级简单) 当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句. 若想关闭这种默认提交方式,让多条SQL在一个事务中执行.就执行以下语句: conn.setAutoCommit(false); //start transaction conn.rollback();//rollback transaction conn.commit();//commit transaction |
4.事务的四大特性
原子性:原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。比如转账:转账前aaa+bbb=2000;转账后aaa+bbb=2000. 隔离性:事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离. 持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的.
|
11、事务隔离级别
1.如果不考虑事务的隔离性,会出现什么问题? 脏读:一个线程中的事务读到了另一个线程中事务未提交的数据. 不可重复读:一个线程中的事务读到了另一个线程中提交的update的数据,前后两次读到的内容不一致. 虚读:一个线程中的事务读到了另一个线程中提交的insert或delete的数据,前后读到的记录条数不一致. PS:主要不同事务之间不能互相影响.  |
2.事务的隔离级别
READ UNCOMMITTED 脏读、不可重复读、虚读都有可能发生 READ COMMITTED 能避免脏读;不可重复读、虚读有可能发生(Oracle默认) REPEATABLE READ 能避免脏读、不可重复读;虚读有可能发生(MySQL默认) SERIALIZABLE 能避免脏读、不可重复读、虚读的发生 以上隔离级别:从上到下,级别越高,性能越低,数据也越安全.
|
3.手动演示脏读、不可重复读、虚读
演示脏读:
时间 | 线程1(自己) READ UNCOMMITTED | 线程2 | 说明 | t1 | start transaction; |
|
| t2 | select * from account where name='aaa';发现:1000元 |
|
| t3 |
| start transaction |
| t4 |
| update account set money=money+100 where name='aaa' |
| t5 | select * from account where name='aaa';发现:1100元 |
| 脏读发生了 | t6 | commit; |
|
| t7 |
| commit; |
|
演示不可重复读:
时间 | 线程1(自己) READ COMMITTED | 线程2 | 说明 | t1 | start transaction; |
|
| t2 | select * from account where name='aaa';发现:1000块 |
|
| t3 |
| start transaction; |
| t4 |
| update account set money=money+100 where name='aaa'; |
| t5 | select * from account where name='aaa';发现:1000块 |
| 避免了脏读 | t6 |
| commit |
| t7 | select * from account where name='aaa';发现:1100块 |
| 发生了不可重复读 | t8 | commit; |
|
|
演示幻读或者虚读:
| 线程1(自己) REPEATABLE READ | 线程2 | 说明 | t1 | start transaction; |
|
| t2 | select * from account;发现:3条记录 |
|
| t3 |
| start transaction |
| t4 |
| insert into account values(4,'ddd',1000); |
| t5 |
| commit; |
| t6 | select * form account;发现4条记录 |
| 幻影数据.发生了欢读。 | t7 | commit; |
|
|
|
4.在JDBC中设置隔离级别
Connection.setTransactionIsolation(int level); |
|
5.丢失更新 问题:什么是丢失更新? 答:a.一个线程中事务覆盖另一个线程中事务已经提交的数据. b.撤销一个事务,影响已提交事务.事实上就是上面的三种情况. 这里主要说明a项中丢失更新的处理.我们可以设置隔离级别来控制丢失更新,但是为了避免错误的读写,还需要学习乐观锁和悲观锁. 悲观锁:其他事务会同时访问资源,就是上锁。它是数据库的锁机制,不是程序模块实现的,因为可能多个系统在动数据库嘛。 ?
1 2 | select * from person for update ;
update person set name = 'ff' w here id= '1'
|
非得提交了之后锁才能释放。所以第一句只要不执行完,其他的事务休想动符合条件的记录(就是该表的数据). 在hibernate里实现悲观锁: ?
1 2 3 4 | String hqlStr = "from TUser as user where user.name='itheima'" ;
Query query = session.createQuery(hqlStr);
query.setLockMode( "user" ,LockMode.UPGRADE); // 加锁
List userList = query.list(); // 执行查询,获取数据
|
类名是TUser 字段是user,原理上还是数据库的for update子句 乐观锁:访问时其他事务修改的概率很低,大不了重读一次。完全靠隔离级别,如果一次操作开销很大,建议用悲观锁。数据库里用版本号实现,说白了就是有个version字段 比如: ?
1 2 3 | select * from person ;
select * from person ;
update person set name = 'xiaoming' ,version=version+1 where id= '1' and version=0;
|
找不到版本号就重新来呗!这就避免了第二类丢失和不可重复读.(SVN版本控制器原理) 举个例子,比如AB都在修改账户里的钱,版本号0,当前100¥,A读了100,扣了20,交上去就是80,版本号1,B如果没同时读,版本号改完就是2,也就没问题了。如果真和A同时读的,也读了个100¥,那它修改后的版 本号就是1,不大于当前版本号,所以不能更新(数据操作删除或更新),只好重新操作一遍咯!这个不难理解吧? 当然乐观锁也会出点小麻烦,因为它是程序实现的,不是数据库实现的,所以脏读是没法避免的。比如这个系统回滚了,别的系统可不知道! 在hibernate里实现 ?
1 2 3 4 5 6 7 8 9 10 11 | < hibernate-mapping >
< class
name = "org.hibernate.sample.TUser"
table = "t_user"
dynamic-update = "true"
dynamic-insert = "true"
optimistic-lock = "version"
>
……
</ class >
</ hibernate-mapping >
|
例子选自http://www.blogjava.net/loocky/archive/2006/11/15/81138.html 这样对象TUser写入关系型数据库用的就是乐观锁了,optimistic-lock="version"表示用版本号控制. |
end...................... |