jdbc封装与多并发的共鸣
欢迎来到:http://observer.blog.51cto.com
代码的封装是一门艺术,封装得好,不但给自己便利,还可以给自己的维护提供帮助;同时,封装得好,还可以给看自己代码的人以赏心悦目的感觉,团队之间的合作可以得到更良好的沟通;封装,不但要给人以便利,还要把自己的需求目的达到,这是一门相当有味的艺术。在此,博主一直热衷于代码的封装,有点经验,共享出来,写得不好之处希望指出,以便改过,觉得写得好的给个赞,有疑问之处请留言,在此先行拜谢!
数据库的使用,现在已经是随处可见,此博文着重于jdbc的封装。
(一)封装的要点:
首先,我们知道jdbc是java数据库连接,数据库的连接,咱们应该让它方便跨数据库。
其次,方便咱们进行获取java.sql.Connection的连接与关闭,以便进行数据库操作。
再其次,咱们应该让它方便我们的事务处理。
最后,也是本博文最关注的一点,便是伴随着数据库封装之后出现的多并发的问题。
(二)封装:
第一:
跨数据库的封装,在此使用配置文件:jdbc.properties存放数据库连接的相关信息,然后封装一个获取数据库连接信息的类:JdbcPropertiesUtil以便信息的读取。
我的数据库为mysql,jdbc.properties文件信息如下:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
jdbc.username=root
jdbc.password=root
我的JdbcPropertiesUtil.java文件如下:
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/*** 获取配置文件中的数据类* @author observer**/
public class JdbcPropertiesUtil {private Properties properties = new Properties();/*** 定义一个制定配置文件名的构造函数* @param propertiesName 需要获取的配置文件名,不包括后缀*/public JdbcPropertiesUtil(String propertiesName) {try {InputStream inputStream =Thread.currentThread().getContextClassLoader().getResourceAsStream(propertiesName+".properties");properties.load(inputStream);inputStream.close();} catch (IOException e) {e.printStackTrace();}}/*** 获取配置文件中数据库的加载class* @return 加载class的字符串*/public String getDriverClassName(){return properties.getProperty("jdbc.driverClassName");}/*** 获取配置文件中数据库连接的url* @return url的字符串*/public String getUrl(){return properties.getProperty("jdbc.url");}/*** 获取配置文件中数据库连接的用户名* @return 用户名的字符串*/public String getUsername(){return properties.getProperty("jdbc.username");}/*** 获取配置文件中数据库连接的密码* @return 密码的字符串*/public String getPassword(){return properties.getProperty("jdbc.password");}
}
在此,如果想进一步封装的话,可以将获取inputStream的参数放在一个固定的配置文件properties中,如:a.properties中配置database.pathname=jdbc,然后根据文件中的参数得到"jdbc",然后再创建inputStream。这样的好处就是,在换数据库时,直接修改database.pathname的值,然后创建其相应名称的properties文件并配置就可以。如将mysql换成oracle,将a.properties中配置database.pathname=oracle;然后创建oracle.pathname并配置好即可。要是想要换回去,不用再创建,直接换database.pathname即可。在这里,小型系统一旦确定数据库,极少会再更改,所以这里就不再过分封装了。
第二:
封装数据库连接类DatabaseUtil。
首先因为数据库驱动的加载只要在该类第一次被访问时加载一次即可,所以在此把数据库驱动加载代码放到了一段静态代码中。
然后就是封装Connection,Connection是数据库的一个连接类,数据库的操作离不开它,楼主的第一个反应就是将该类的对象设置为一个静态变量,这样就可以一劳永逸了,不用再每次需要进行数据库操作时都对数据库连接一次,而且可以节省每次连接数据库的时间。但是,没有happy多久,这个想法就被虐杀在摇篮中,因为有事务处理与多并发带来的各种问题。
举个很经典的例子,一个银行账户,在一台计算机上进行转账,在另一台计算机上进行网购,当转账转到一半时(此时刚好将来源账户的钱扣除,但是没有将钱打进目标账户),另一台计算机进行网购,然后将事务commit了;因为从始至终只有一个Connection,所以网购完成了,同时这边的转账出现异常也在中途结束了,最终的结果就是转账失败了,但是钱却没了。
通常的情况下,就是不设任何Connection,这样事情当然也就没有了。但是,这样做的话,与数据库打交道的类中(如DAO,以下均称DAO),每一项DAO的方法都不会知道后面的应用中是否会需要进行事物处理,也就是说DAO的每一个方法都需要传进一个Connection参数。同时,每一次需要用到DAO时,不管是否需要进行事务处理,都要在业务逻辑中得到Connection,然后才能调用DAO。这显然用着体验不是很好,起码楼主是这样觉得。
这个问题,曾经也困扰着楼主一段时间,直到某一天发现了一个很有趣的API:java.lang.ThreadLocal。该类有一个特性,那就是如果将该类的对象设为static,那么不同的线程访问它时,它都会生成一个局部变量,它独立于变量的初始化副本;意思就是说,每一个线程第一次访问它时都是不同的一个副本,而同一个线程再次访问时,访问的还是上一次访问的副本,祥情可以产看java的API(原理上是否这样暂且不管,只要知道达到了这个效果就成)。这么有意思的API,简直是相见恨晚!就跟为事务处理与多并发封装量身定制的一样。
既然有这么好用的一个API,那么封装条件就已经具备了,咱们说做就做,先上代码再解释:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*** 数据库连接工具类* @author observer*/
public class DatabaseUtil {private static JdbcPropertiesUtil jpu = new JdbcPropertiesUtil("jdbc");
// private static final ThrL threadLocal = new ThrL();private static final ThreadLocal<AutoCommit> threadLocal =new ThreadLocal<AutoCommit>();static {try {Class.forName(jpu.getDriverClassName());} catch (ClassNotFoundException ex) {System.out.println("数据库驱动加载失败");}}
// static class ThrL{
// public static AutoCommit autoCommit= null;;
// public AutoCommit get(){
// return this.autoCommit;
// }
//
// public void set(AutoCommit autoCommit){
// this.autoCommit = autoCommit;
// }
//
// public void remove(){
// autoCommit = null;
// }
// }/*** 事务管理辅助类*/private static class AutoCommit{private Connection conn = null;private boolean autoCommit = true;public void close(){if (conn != null) {try {conn.close();conn = null;autoCommit = true;} catch (SQLException e1) {e1.printStackTrace();}}}}/*** 如果threadLocal中已经存在AutoCommit(如调用过begin方法),这返回AutoCommit中的Connection,* 否则新建一个AutoCommit,然后创建一个Connection放到AutoCommit中,* 然后把AutoCommit放到threadLocal中,并返回Connection* @return 连接好数据库的java.sql.Connection*/public static Connection getConnection() {AutoCommit autoCommit = threadLocal.get();if(autoCommit==null){autoCommit = new AutoCommit();try {autoCommit.conn = DriverManager.getConnection(jpu.getUrl(),jpu.getUsername(), jpu.getPassword());threadLocal.set(autoCommit);} catch (SQLException e) {autoCommit.close();threadLocal.remove();e.printStackTrace();}}return autoCommit.conn;}/*** 如果threadLocal中的AutoCommit不为null,* 这创建一个AutoCommit并且创建数据库连接Connection并打开事务管理* 然后将AutoCommit放到threadLocal中*/public static void begin(){AutoCommit autoCommit = threadLocal.get();if(autoCommit==null){autoCommit = new AutoCommit();threadLocal.set(autoCommit);try {autoCommit.conn = DriverManager.getConnection(jpu.getUrl(),jpu.getUsername(), jpu.getPassword());autoCommit.conn.setAutoCommit(false);autoCommit.autoCommit = false;} catch (SQLException e) {autoCommit.close();threadLocal.remove();e.printStackTrace();}}}/*** 关闭java.sql.Connection*/public static void colseConn() {AutoCommit autoCommit = threadLocal.get();if (autoCommit.autoCommit && autoCommit.conn!=null) {autoCommit.close();threadLocal.remove();}}/*** 关闭java.sql.Statement* @param stml 需要关闭的java.sql.Statement*/public static void colseStml(Statement stml) {if (stml != null) {try {stml.close();} catch (SQLException e1) {e1.printStackTrace();}}}/*** 关闭java.sql.ResultSet* @param rs 关闭java.sql.ResultSet*/public static void colseRs(ResultSet rs) {if (rs != null) {try {rs.close();} catch (SQLException e1) {e1.printStackTrace();}}}/*** 当事务管理启动后,调用此方法进行提交事物,并且关闭java.sql.Connection*/public static void commit(){AutoCommit autoCommit = threadLocal.get();if(!autoCommit.autoCommit && autoCommit.conn!=null){try {autoCommit.conn.commit();} catch (SQLException e) {e.printStackTrace();}finally{autoCommit.close();threadLocal.remove();}}}/*** 当事务处理失败时调用此方法进行事务回滚,并且关闭java.sql.Connection*/public static void rollback(){AutoCommit autoCommit = threadLocal.get();if(!autoCommit.autoCommit && autoCommit.conn!=null){try {autoCommit.conn.rollback();} catch (SQLException e) {e.printStackTrace();}finally{autoCommit.close();threadLocal.remove();}}}
}
以上工具类中,定义一个ThreadLocal,ThreadLocal中放AutoCommit,AutoCommit是自己定义的一个内部类,该类用于辅助事务管理,定义Connection与autoCommit属性。每一个不同的线程第一次访问ThreadLocal时,里面的AutoCommit都为null,同一个线程再次访问时,如果上一次访问时没有remove,那么这一次访问的照样是同一个AutoCommit;
定义getConnection方法,如果不用进行事务处理,那么直接调用该方法创建Connection;
定义begin方法,如果需要进行事务管理,那么在事务前调用该方法,创建一个Connection,并将内部类AutoCommit的autoCommit属性设为false,将Connection的自动提交关闭,当需要进行数据库处理时,该线程调用getConnection方法得到的Connection就是已经关闭自动提交的Connection;
定义colseConn,如果自动提交已经关闭,那么调用该方法是不能关闭Connection的,只能调用后面定义的commit方法才能进行事务的提交并且关闭Connection。
然后就是定义rollback,该方法用于在事务提交失败时进行事务回滚,并且关闭Connection与清空AutoCommit与ThreadLocal;
更具体的东西与理解,可以下载附件看源码。
(三)验证:
在代码中是不是看到一些注释掉的代码?不急,咱们说再多的理论都没用,事实胜于雄辩。咱对该工具类做一下验证。
第一:搭建测试环境
首先在上面的工具类中,有一些注释,该注释掉的是一个仿ThreadLocal的内部类,它没有ThreadLocal类的多线程特性,只是一个普通的类,如果将private static final ThreadLocal<AutoCommit> threadLocal = new ThreadLocal<AutoCommit>();这句代码注释掉,换成private static final ThrL threadLocal = new ThrL();那么我们就相当于得到了一个将Connection当作静态属性的DatabaseUtil工具类。现在我们把第一种ThreadLocal假设为模式A,把ThrL假设为模式B;
创建mysql数据库(自己创建自己对应的数据库与配置相对应的数据库配置文件):
create database test;
user test;
create table testuser(
toid int not null auto_increment primary key comment '主键,自动增长',
testname varchar(20) not null comment '用户名',
remaining int comment '用户余额'
);
insert into testuser(testname, remaining) value("observer1",1000);
insert into testuser(testname, remaining) value("observer2",1000);
第二:创建依赖类
在这里楼主创建了pojo,dao接口,daoImpl,error还有manager,这里就不一一解释了,因为这些不创建也是可以的,很容易理解,不过只要有条件,楼主喜欢把他们封装好(可以下载附件源码看),咱注重于多并发带来的问题,就检重要的来讲。
在此贴出manager以便解释:
import com.observer.database.dao.TestUserDao;
import com.observer.database.dao.impl.TestUserDaoImpl;
import com.observer.database.errer.DAOException;
import com.observer.database.errer.FormatException;
import com.observer.database.model.TestUser;
import com.observer.database.util.DatabaseUtil;
/*** TestUser的业务逻辑管理类* @author observer*/
public class TestUserManager {private static TestUserDao testUserDao = new TestUserDaoImpl();/*** 取款方法,判断用户中余额是否够取款金额取款,如果够则取款,否则抛出异常,取款失败* @param toid 需要取款账户的toid* @param money 需要取款的金额* @throws FormatException 余额不足时抛出com.observer.database.errer.FormatException* @throws DAOException 取款时出现SQL错误时回滚事务并抛出com.observer.database.errer.DAOException* @throws Exception 取款时出现FormatException,DAOException以外的异常时回滚事务并抛出java.lang.Exception*/public static void drawMoney(int toid, float money) throws FormatException,DAOException, Exception{try{//取款之前停止一下,让其他线程先运行,模拟真实情况try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}DatabaseUtil.begin();TestUser test = testUserDao.find(1);if(test.getRemaining()-money>=0){testUserDao.draw(test.getToid(), money);}else{throw new FormatException("取款失败", "您的余额不足");}DatabaseUtil.commit();}catch(DAOException e){DatabaseUtil.rollback();e.printStackTrace();throw e;}catch (Exception e) {DatabaseUtil.rollback();e.printStackTrace();throw e;}}/*** 转账取款方法,判断来源用户余额是否够转账金额转账,如果够则进行转账,否则抛出异常,转账失败* @param fromId 转账来源账户* @param toId 转账目标账户* @param money 转账金额* @throws FormatException 来源用户余额不足时抛出com.observer.database.errer.FormatException* @throws DAOException 转账时出现SQL错误时回滚事务并抛出com.observer.database.errer.DAOException* @throws Exception 转账时出现FormatException,DAOException以外的异常时回滚事务并抛出java.lang.Exception*/public static void transfer(int fromId, int toId, float money) throws FormatException, DAOException, Exception{try {DatabaseUtil.begin();TestUser test = testUserDao.find(fromId);if(test.getRemaining()-money>=0){testUserDao.draw(fromId, money);//当转账到一半时停止一下,让其他线程运行,模拟真实情况try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}if(true){throw new DAOException("断电了", "断电了,转账失败");}testUserDao.storage(toId, money);DatabaseUtil.commit();}else{throw new FormatException("转账失败", "您的余额不足");}}catch(DAOException e){DatabaseUtil.rollback();e.printStackTrace();throw e;} catch (Exception e) {DatabaseUtil.rollback();e.printStackTrace();throw e;}}
}
以上业务逻辑管理类中,分别为取款与转账,在取款中设置Thread.sleep(1000);让转账先运行,然后在转账到一半时(此时刚好将来源账户的钱扣除,但是没有将钱打进目标账户),设置Thread.sleep(2000);让取款运行,最后自己再运行,然后出现异常,模拟刚才的转账与取款的例子。
第三:编写测试类
因为serlvet可是以多线程的方式运行的,现在使用多线程模拟一下,创建类如下:
import com.observer.database.errer.DAOException;
import com.observer.database.errer.FormatException;
import com.observer.database.manager.TestUserManager;
/*** 测试方法* @author observer**/
public class Test {public static void main(String[] args) {new Thread(new Transfer(1, 2, 100)).start();new Thread(new GetManey(1,100)).start();}
}
/*** 定义一个线程类,测试取款* @author observer**/
class GetManey implements Runnable{private int toid;private float money;public GetManey(int toid,float money){this.money = money;}public void run() {try {TestUserManager.drawMoney(toid, money);System.out.println("取款成功 : "+money);} catch (FormatException e) {e.printStackTrace();System.out.println(e.getTitle()+" : "+e.getDetails());}catch(DAOException e){e.printStackTrace();System.out.println("取款失败 : "+e.getDetails());} catch (Exception e) {e.printStackTrace();System.out.println("取款失败 : 请从新再试");}}
}
/*** 定义一个线程类测试转账* @author observer**/
class Transfer implements Runnable{private int fromId;private int toId;private float money;public void run() {try {TestUserManager.transfer(fromId, toId, money);System.out.println("转账成功 : "+money);} catch (FormatException e) {e.printStackTrace();System.out.println(e.getTitle()+" : "+e.getDetails());}catch(DAOException e){e.printStackTrace();System.out.println("转账失败 : "+e.getDetails());} catch (Exception e) {e.printStackTrace();System.out.println("转账失败 : ");}}public Transfer(int fromId, int toId, float money) {this.fromId = fromId;this.toId = toId;this.money = money;}
}
第四:测试
好了,一切就绪,咱们首先测试模式B,出现如下情况:
查看数据库如下:
好吧!问题终于出现了。咱们再用模式A试试,把private static final ThrL threadLocal = new ThrL();注释掉,然后使用private static final ThreadLocal<AutoCommit> threadLocal = new ThreadLocal<AutoCommit>();
在数据库中将toid=1的数据修改到1000:update testuser set remaining=1000 where toid=1;然后运行测试得到如下结果:
查看数据库如下:
事实证明了,没有任何问题,数据非常完整,happy一下。
第五:另一个问题
到此,楼主没有happy多久,又发现了另外一个问题,那就是存款余额的最低限制。当转账的金额与取款的金额刚刚好为存款的余额时,如果出现以上例子,而且中途没有产生异常,那么存款就会变为负值,这是相当大的一个bug。
咱们来测试一下,先将以下代码注释掉:
// if(true){
// throw new DAOException("断电了", "断电了,转账失败");
// }
这样就没有断电的异常了,然后将转账与存款金额改为存款余额,如:1000,运行得到以下结果:
查看数据库如下:
好了,要是金额多一点的话,相当于我可以不用办任何手续贷款了......
解决这个问题,一个是在查询数据到修改数据之间进行线程锁的锁住,但是这样做会妨碍到多并发的运行,不是一个良好的决绝方案。
在此,楼主的解决方案就是限制数据库的字段范围,设定其存款余额不能小于0,否则会出现异常,这样的话,哪个线程先提交数据库处理,那么就由哪个线程的处理成功。
随后设置数据库表如下:
drop table testuser;
create table testuser(
toid int not null auto_increment primary key comment '主键,自动增长',
testname varchar(20) not null comment '用户名',
remaining int unsigned comment '用户余额'
);
insert into testuser(testname, remaining) value("observer1",1000);
insert into testuser(testname, remaining) value("observer2",1000);
然后运行测试得到如下结果:
数据库里面的数据如下:
到此,数据非常完整,而且反馈信息也都正确,终于可以happy了。
(源码请到这里下载:http://down.51cto.com/data/896293)
到此,本封装就已经结束了,封装这门艺术,多姿多彩,不一定就一定要像我这样封装,说不定哪天博主功力有所提升,或者哪位朋友,觉得哪样封装更好,再次封装也是正常的事情。当然了,其实要真想应用到实际中去,那还是直接下载个数据库连接池用着痛快,连接池技术真的是没得说。
注:本博文仅能为理解底层逻辑提供参考,大道在于连接池......
转载于:https://blog.51cto.com/observer/1263577
相关文章:

计算机视觉怎样实现自我超越?更大规模更精准的数据
最新发布的《2021中国人工智能应用趋势报告》强调,数据、算力和算法是支撑人工智能发展的"三驾马车",为模型训练提供基本资料的「数据」,是人工智能的根基。 随着互联网、社交媒体、移动设备和传感器的大量普及,其产生…

Visual Studio 2005 Web Deployment Projects版本不同引发的问题
为了方便Visual Studio 2005发布为单一dll,微软发布了一个Visual Studio 2005 插件,Visual Studio 2005 Web Deployment Projects,在微软的不同文档里,这个插件提供了两个下载地址,分别是: 下载地址一&…

【书籍下载链接】_2_第二轮_计算机专业书籍
各位朋友,下面是我收集的书籍,介绍给大家,有需要可以分享给大家,如果看的还可以,请购买纸质版的图书。 驱动器 J 中的卷是 Elements 卷的序列号是 8AAF-3206 j:\ 的目录 2014/01/20 20:00 1,533,385 WinCE.pdf2010/09/…

VS2005发布、生成网站时如何设置固定的dll文件名?
在用VS2005发布网站项目时,默认生成bin目录下的.dll文件名是随机命名的; 如果要固定生成文件名如何固定呢?有以下两种方案: 一、每个页面的程序集分别生成对应的dll; 方法:在“发布网站”的选项中,勾选“使用固定命名和单页程…

android 广播机制
1:首先说andoid 广播分为系统的和 自定义的 2:注册方式呢,也是两种,1:静态注册,在manifest.xml 文件中注册的 2:动态注册,用filter 区分 不说了 占代码 首先是动态注册:…
2021第一融!第四范式完成D轮7亿美元融资
来源丨第四范式头图丨来源于第四范式近日,第四范式宣布完成D轮融资,融资金额7亿美元。本轮融资由春华资本、博裕资本、厚朴投资领投,并引入国家制造业转型基金、国开、国新、中国建投、中信建投、海通证券等战略股东,红杉中国、中…

springboot-26-springboot 集成rabbitmq
rabbitmq是基于AMQP规范的一个消息代理, 它可以兼容jms, 支持其他语言, 并且可以跨平台 1, 安装 1) 普通安装 度娘: 2) docker 安装 sudo docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management 安装成功后: 使用 guest/guest 用户登录 2 使用: 1) 添加 rabbi…

asp.net中的联动菜单
目标达到的效果:两个下拉框,第二个跟随第一个变化而变化,使用客户端脚本JavaScript在ASP.NET环境下实现。 第一步:建立JavaScript脚本: 在Page_Load中建立并注册这个js脚本: string scriptKey "Menu…
2020长沙“科技之星”榜单重磅揭晓,近百家企业凭实力“出道”!
今天,「INFLUENCE长沙 2020年度“科技之星”企业评选」(下文统称「长沙科技之星」)圆满收官,评选结果正式揭晓!作为专业的 IT 社区,CSDN 多年来与千万技术人员、技术企业共同见证了产业的发展和时代的变更…

CENTOS6.4 IBUS输入法不显示候选词解决办法
IBUS输入法 不显示候选词原因分析:输入im-chooser时候,显示找不到gtk模块;原因为升级python后的版本,不能导入gtk。找到能够导入gtk版本的python,然后默认python设置为此版本。故障解决:删除或更改默认python版本# whi…

sql server 表索引碎片处理
DBCC SHOWCONTIG (Transact-SQL) SQL Server 2005 其他版本更新日期: 2007 年 9 月 15 日 显示指定的表或视图的数据和索引的碎片信息。 重要提示:后续版本的 Microsoft SQL Server 将删除该功能。请避免在新的开发工作中使用该功能,并着手修…

ASP.NET2.0 GridView小技巧汇粹
1)GridView绑定数据源控件,需要有编辑和删除选项按钮时,数据源控件必须提供SQL操作语句或存储过程调用,一般,我的推荐做法是,使用无意义的SQL语句或存储过程来使GridView的编辑和删除按钮可以生成,具体的编辑更新和删除操作在代码运行时而不是在控件设计时指定,虽然多写了一点代…
树莓派出微控制器了!Raspberry Pi Pico 只需 4 美元
整理 | 郑丽媛来源 | CSDN(ID:CSDNnews)昨天,树莓派搞了个大动作:推出了首款微控制器开发板 Raspberry Pi Pico!该开发板基于树莓派开发的全新芯片——RP2040,并且作为双核 Arm Cortex-M0 的它…

“chaos”的算法--之链表面试题
【 声明:版权所有,欢迎转载。 联系信箱:yiluohuanghungmail.com】前两天倩仔仔给我了一套试题让我看,整体来说感觉题都还算不错,从中随便找了两道。先看题吧!1、怎样判断一个单链表中是都存在环路ÿ…

ABP官方文档翻译 6.1.2 MVC视图
ASP.NET MVC 视图 介绍AbpWebViewPage基类介绍 ABP通过Abp.Web.Mvc nuget包集成到MVC视图。你可以如往常一样创建正常的MVC视图。 AbpWebViewPage基类 ABP提供了AbpWebViewPage,它定义了一些有用的属性和方法。如果你使用启动模板创建的工程,那么你所有的…

ASP.NET 打开新窗口几种方法
ASP.NET打开新窗口方法一:Response.Write("<script language/"javascript/">window.open(aaa.aspx,新窗口,/"toolbaryes,locationno,directoriesyes,statusyes,menubaryes,resizableyes,scrollbarsyes/");</script>");这种方式代码每…

Hibernate的使用梳理
Hibernate创建步骤 (五大核心接口:Configuration/SessionFactory/Session/Transaction/Query) 1.新建java工程,导入需要的jar包。 2.创建hibernate.cfg.xml配置文件和Test.java工具类。配置好相应的实体对象User.java User.hbm.x…
驭势科技引入国家队战略注资,完成超10亿元人民币融资
2021年1月25日,驭势科技(UISEE)宣布完成累计金额超10亿元人民币的新一轮融资,并获得国开制造业转型升级基金的战略注资。这是国开制造业转型升级基金在自动驾驶领域的首笔投资。2019年11月,国家制造业转型升级基金股份…

[Python爬虫] 之二十二:Selenium +phantomjs 利用 pyquery抓取界面网站数据
一、介绍 本例子用Selenium phantomjs爬取界面(https://a.jiemian.com/index.php?msearch&aindex&typenews&msg电视)的资讯信息,输入给定关键字抓取资讯信息。 给定关键字:数字;融合;电视 抓取…

android高级编程-android高级应用
android高级应用>>>第一阶段程序员基本素质养成程序员所需要具备的12条职业素质让学员初步了解和审视自己所应该具备的职业素质。并且我们会在授课中随时训练和贯彻这样的素质,最终把大家捏成专业的职业的程序员。迭发各个环节及工具初步介绍总概性的讲解一…

asp.net三种重定向方法的总结
(1)Server.Transfer方法: Server.Transfer("m2.aspx");//页面转向(服务器上执行). 服务器停止解析本页,保存此页转向前的数据后,再使页面转向到m2.aspx, 并将转向前数据加上m2.aspx页结果返回给浏览器. (2)Server.Execute方法: Server.Execute("m2.aspx"…
区区几行Python代码,一分钟搞定一天工作量
作者 | 陈熹、刘早起来源 | 早起Python大家好,我是早起。前几天有一个读者说最近要整理几千份文件,头都要整秃了,不知道能不能用Python解决,我们来看一下,你也可以思考一下。由于涉及文件私密所以具体内容已做脱敏处理…

bc计算命令的知识及企业计算案例
bc命令的用法:bc是unix下的计算器,它也可以用在命令行下面:例:给自变量i加1i2iecho $i1|bc -----效率低#因为bc支持科学计算,所以这种方法功能非常强大[rootXCN ~]# echo 11|bc 2 [rootXCN ~]# echo 1*1|bc 1 […

ExecutorService与Executors例子的简单剖析(转)
对于多线程有了一点了解之后,那么来看看java.lang.concurrent包下面的一些东西。在此之前,我们运行一个线程都是显式调用了 Thread的start()方法。我们用concurrent下面的类来实现一下线程的运行,而且这将成为以后常用的方法或者实现思路。 …

GridView隐藏列取值解决方案
【摘要】 在Asp.net 2.0中增加了一个新的数据绑定控件:GridView,其目的用来取代Asp.net1.x中的DataGrid控件,但有一点很不爽的是,如果把某列设置为visiblefalse,则不会进行数据绑定,也就是说无法直接从Grid…

百度飞桨成为北京市首个AI产业方向创新应用平台
1月20日,北京市经济和信息化局正式授予百度公司"北京市人工智能产业创新应用平台(百度飞桨)"。当前,北京市正在创建国家人工智能创新应用先导区,人工智能作为新科技革命和产业变革前沿领域,是北京…

FTP的20、21端口,工作模式
什么是FTP? FTP就是文件传输协议 File Transfer Protocol 的缩写. FTP端口号是多少? 21 FTP的端口号能改吗? 能 ftp的端口号20、21有何区别? 一个是数据端口,一个是控制端口,控制端口一般为21,而数据端口不一定是20,这和FTP的…

android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动&#…
物联网下的数据传输,Python 就能搞定!
作者 | 李秋键责编 | 夕颜出品 | AI科技大本营(ID:rgznai100)引言:近几年来,谈起发展最火热的几个关键词必然是人工智能、大数据以及物联网的万物互联、边缘计算等等了。而今天,我们就将利用Python实现物联网下的数据传…

ASP.NET 2.0中Page事件的执行顺序
Page 执行中将按照如下顺序激活事件:Page.PreInitPage.InitPage.InitComplitePage.PreLoadPage.LoadPage.LoadCompletePage.PreRenderPage.PreRenderComplete如果页面从令一个页面继承,如BasePage:System.Web.UI.Page,在BasePage中做了一些扩…