JavaEE的三大组件
一、Servlet
JDBC全称为Java Data Base Connectivity(java数据库连接),定义了一套Java操作数据库的规范(接口),统一对数据库的操作。这套接口由数据库厂商去实现,开发人员只需要通过JDBC接口操作不同的数据库,屏蔽了不同数据库之间的语法区别。
1.JDBC简单实现过程
1.加载数据库驱动
2.通过驱动获取connect数据库连接
3.通过数据库连接获取可执行SQL的Statement
4.通过Statement对象执行SQL
5.关闭链接,释放资源
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://localhost:3306/test";//连接的数据库URL
String username = "root";//连接的数据库用户名
String password = "123456"; //连接的数据库密码
//1.加载数据库驱动(要引入驱动的jar包,例如:mysql-connector-java-3.1.10-bin.jar)
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推荐使用这种方式来加载驱动
Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动
//2.通过驱动获取connect数据库连接
Connection conn = DriverManager.getConnection(url, username, password);
//3.通过数据库连接获取可执行SQL的Statement
Statement st = conn.createStatement();
String sql = "select id,name,password,email,birthday from users";
//4.通过Statement对象执行SQL
ResultSet rs = st.executeQuery(sql);
......
//5.关闭链接,释放资源
rs.close();
st.close();
conn.close();
}
2.JDBC在工程中的实现
在工程中,为了程序的可复用性、可移植性等原因,一般要做一些配置和封装
1.首先数据库参数配置一般不直接写在代码里,通常以配置文件的形式出现(如db.properties)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=XDP
2.一般将获取连接和释放连接封装为JDBCUtils.java工具类
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static{
try{
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);
//获取数据库连接配置
driver = prop.getProperty("driver");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
//加载数据库驱动
Class.forName(driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
// 获取数据库连接对象
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username,password);
}
//释放资源
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.PreparedStatement对象
PreperedStatement和Statement的比较:
1.PreperedStatement是Statement的子类,也可以通过connect获得,也可以执行SQL
2.PreperedStatement对SQL进行预编译,从而提高数据库的执行效率;而Statement会使数据库频繁编译SQL
3.PreperedStatement对sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写;而Statement需要复杂地拼接SQL字符串
4.PreperedStatement相比于Statement可以避免SQL注入的问题,典型的SQL注入问题随意登录
经典的PreparedStatement用法
conn = JdbcUtils.getConnection();
//要执行的SQL命令,SQL中的参数使用?作为占位符
String sql = "insert into users(id,name) values(?,?)";
//通过conn对象获取负责执行SQL命令的prepareStatement对象
st = conn.prepareStatement(sql);
//为SQL语句中的参数赋值,注意索引是从1开始的
st.setInt(1, 1);
st.setString(2, "CSDN");
//执行操作
......
注意此处的占位符索引,在Java中从1开始,在C#中从0开始
原始的JDBC是学校课堂上的基础知识,也是市场框架或者主流开发技术的基石
二、数据库连接池
应用程序每次做数据库操作都需要先获得连接,再释放。数据库创建连接通常需要消耗较大的系统资源,程序耗时也较长。并发访问量较大时,会创建较多的资源,可能会导致数据库服务器内存溢出甚至宕机。
对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。
1.简单的数据库连接池实现
编写连接池需实现DataSource接口,在类构造函数中批量创建数据库的连接,并把连接加入LinkedList对象中。实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。当用户使用完Connection,调用Connection.close()方法时,Collection对象返回到LinkedList中
public class MyDataSource implements DataSource {
//链表
privateLinkedList<Connection> dataSources = new LinkedList<Connection>();
//初始化连接数量
publicMyDataSource() {
for(int i = 0; i < jdbcPoolSize; i++) {
try {
Class.forName("com.mysql.jdbc.Driver");
Connection con =DriverManager.getConnection(url, username, password);
dataSources.add(con);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public Connection getConnection() throws SQLException {
final Connection conn = dataSources.removeFirst(); // 取出连接池中一个连接,删除第一个连接返回
return conn;
}
public void releaseConnection(Connection conn) {
dataSources.add(conn);//将连接放回连接池
}
}
而一个高可用高性能的数据库连接池远没有这么简单,实际上连接池中连接的归还是由动态代理实现,除此以外还要考虑更多的问题:
- 并发问题
- 多数据库服务器和多用户
- 事务处理
- 连接池的分配与释放
- 连接池的配置与维护
可以参考这里查看这篇博文查看更详细的解释
2.常见的开源数据库连接池
1、DBCP数据库连接池
DBCP是Apache下开源的数据库连接池组件,Tomcat的连接池正是采用该连接池来实现的
2、C3P0数据库连接池
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定。C3P0数据源在项目开发中使用得比较多,是Hibernate内置的数据库连接池,使用C3P0的还有Spring。本人毕设时在Tomcat环境JSP+Servlet+JavaBean模式下就是用的C3P0连接池。C3P0有多种配置方式:JavaAPI、文件配置等
需要引入相关的jar包,c3p0-0.9.2-pre1.jar和mchange-commons-0.2.jar
- Java API方式配置
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl("jdbc:mysql://host:port/db");
cpds.setUser("username");
cpds.setPassword("password");
cpds.setMinPoolSize(5);
cpds.setMaxPoolSize(20);
cpds.setAcquireIncrement(5);
Connection conn = cpds.getConnection();// 直接从连接池中获取连接
query(conn);
cpds.close();
- properties文件配置
需要在classpath路径下添加配置文件:c3p0.properties。创建ComboPooledDataSource对象时,C3P0自动从classpath加载c3p0.properties中的配置信息。
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://host:port/db
c3p0.user=root
c3p0.password=
c3p0.minPoolSize=5
c3p0.maxPoolSize=20
c3p0.acquireIncrement=5
使用c3p0.properties作为配置文件时,每个参数的name前缀必须是“c3p0”
- c3p0-config.xml文件配置
c3p0-config.xml配置文件比properties更高级,可配置多数据源。创建ComboPooledDataSource对象时,C3P0自动从classpath加载c3p0.properties中的配置信息
文件名必须为c3p0-config.xml,常用的设置参数以及作用如下
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://host:port/db</property>
<property name="user">username</property>
<property name="password">password</property>
<!-- 连接池初始化时创建的连接数 -->
<property name="initialPoolSize">3</property>
<!-- 连接池保持的最小连接数 -->
<property name="minPoolSize">3</property>
<!-- 连接池中拥有的最大连接数,连接总数超过这个值则等待其他连接释放 -->
<property name="maxPoolSize">15</property>
<!-- 连接池在无空闲连接可用时一次性创建的新数据库连接数 -->
<property name="acquireIncrement">3</property>
<!-- 连接的最大空闲时间 0则永远不会断开 单位: 秒 -->
<property name="maxIdleTime">0</property>
<!-- 用来配置测试空闲连接的间隔时间。可以用来解决MySQL 8小时断开连接的问题。因为它保证连接池会每隔一定时间对空闲连接进行一次测试,从而保证有效的空闲连接能每隔一定时间访问一次数据库,将MySQL8小时无会话的状态打破。为0则不测试。默认值:0,单位: 秒 -->
<property name="idleConnectionTestPeriod">30</property>
</default-config>
<!-- 定义带名称的数据源 -->
<named-config name="myDataSource">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test_jdbc</property>
<property name="user">root</property>
<property name="password"></property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
<property name="acquireIncrement">5</property>
</named-config>
</c3p0-config>
3、Druid数据库连接池
Druid是阿里巴巴国产连接池,高效、功能强大、可扩展性好,可替换替换DBCP和C3P0。通常在Spring项目中配置使用
Druid包括三个部分:基于Filter-Chain模式的插件体系、DruidDataSource数据库连接池、SQLParser
- 监控数据库访问性能
Druid内置提供了一个StatFilter插件,能够详细统计SQL的执行性能 - 数据库密码加密
- SQL执行日志
Druid提供了不同的LogFilter,监控应用的数据库访问情况。
有时间需要研究Druid详细的使用方法,目前市场主流应用连接池。
4、weblogic商用数据库连接池
虽然Druid连接池在数据库监控,在性能上都比较高效,但商用的weblogic连接池更稳定高效,而且有更多更复杂的功能实现。值得一说的是Test Reserved Connections:对取得的连接进行测试的功能。在项目开发中可能会有java报连接失效的异常,或者获取到失效连接导致SQL执行有问题再重新获取连接的情况,这都会影响应用使用性能。
三、配置Tomcat数据源
如果我们希望Tomcat服务器在启动的时候可以创建一个数据库连接池,那么我们在应用程序中就不需要手动创建,直接使用即可。
1、JNDI技术
JNDI(Java Naming and Directory Interface),Java命名和目录接口。这套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可。其核心API为Context,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
Tomcat服务器创建好数据源之后是以JNDI的形式绑定到JNDI容器中的。
日常javaEE开发,服务器会创建很多资源,比如request对象,response对象,这些资源有两种方式使用:一是通过方法参数的形式传递进来,比如Servlet中使用到的request对象和response对象。二是JNDI的方式,直接从JNDI容器中获取相应的资源即可
对JNDI容器很感兴趣,估计会和spring容器有相似之处,可能也会对了解Tomcat底层原理有帮助
2、配置Tomcat数据源步骤
1、在Web项目的WebRoot目录下的META-INF目录下创建一个context.xml文件如下
<Context>
<Resource
name="jdbc/datasource"
auth="Container"
type="javax.sql.DataSource"
username="root"
password="XDP"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbcstudy"
maxActive="8"
maxIdle="4"/>
</Context>
2、在tomcat的lib目录下放置数据库的驱动jar包
3、在获取数据库连接的工具类中获取JNDI容器中的数据源
private static DataSource ds = null;
//在静态代码块中创建数据库连接池
static{
try{
//初始化JNDI
Context initCtx = new InitialContext();
//得到JNDI容器
Context envCtx = (Context) initCtx.lookup("java:comp/env");
//从JNDI容器中检索name为jdbc/datasource的数据源
ds = (DataSource)envCtx.lookup("jdbc/datasource");
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}