JDBC第四次学习
传智播客李勇老师的JDBC系列学习终于接近尾声了,好开心,能学到这么多的东西,还不赶快记录下来,留待以后回味!
如何使用开源项目DBCP(实际项目中常用)
主要分为三个步骤:
- 使用DBCP必须用的jar包。
- 添加dbcp的配置文件。
- Java API:BasicDataSourceFactory.createDataSource(prop);
不然会报异常:
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
初步原因是因为少导入了commons-logging.jar,导入此jar问题则会解决!
dbcp的配置文件(dbcpconfig.properties)如下:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
username=root
password=yezi #<!-- 初始化连接 -->
initialSize=10 #最大连接数量
maxActive=50 #<!-- 最大空闲连接 -->
maxIdle=20 #<!-- 最小空闲连接 -->
minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
代码如下:
public final class JdbcUtils {
private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true";
private static String user = "root";
private static String password = "yezi"; //private static DataSource myDataSource = new MyDataSource2();
private static DataSource myDataSource = null; private JdbcUtils() { }
/*
* 静态代码块
* 随着类的加载而执行,只执行一次,并优先于主函数。用于给类进行初始化。
*/
static {
try {
Class.forName("com.mysql.jdbc.Driver");
//myDataSource = new MyDataSource2();
Properties prop = new Properties();
/*
* 写死在程序里
*/
//prop.setProperty("driverClassName", "com.mysql.jdbc.Driver");
//prop.setProperty("user", "root"); InputStream is = JdbcUtils.class.getClassLoader().
getResourceAsStream("dbcpconfig.properties");
prop.load(is);
myDataSource = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
} //返回数据源,因为我们的程序只和DataSource打交道,不会直接访问连接池
public static DataSource getDataSource() {
return myDataSource;
} public static Connection getConnection() throws SQLException {
//return DriverManager.getConnection(url, user, password);
return myDataSource.getConnection();
}
public static void free(ResultSet rs, Statement st, Connection conn) {
//比较规范的释放方式,比较麻烦
try {
if(rs != null)
rs.close();
} catch(SQLException e) {
e.printStackTrace();
} finally {
try {
if(st != null)
st.close();
} catch(SQLException e) {
e.printStackTrace();
} finally {
if(conn != null)
try {
/*
* close()方法就相当于将连接放进连接池
* 即调用close()就相当于调用myDataSource.free(conn);
*/
conn.close();
//myDataSource.free(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
在我们之前的示例中我们的MyDataSource可以实现Datasource接口,实现里面的getConnection()方法,我们想用dbcp的时候,把数据源的实现换成dbcp就行,因为这个组件也实现了DataSource接口,所以这就是面向接口编程的好处,可以换成不同的实现 ,不用改变我们其他的代码。
将DAO中的修改方法提取到抽象父类中
当你在写程序的时候,如果你发现你的代码总是有重复的地方那么就有必要封装一下了。把一段代码中的变化的部分抽取到父类里,把要用的参数传递过去,然后在实现类里面直接super调用父类的方法就可以了,记得把参数传递过去就行了。可以向外提过多个方法的重载,但是真正的代码实现只有一份。
代码就不赘述了,下面会贴出来。
使用模板方法设计模式处理DAO中的查询方法
有关模板方法设计模式,请移步我的《面向对象之继承》,有一点介绍,故在此不赘述了。
什么都不说,直接上代码:
AbstractDao类:
/*
* 模板设计模式
*/
public abstract class AbstractDao { public Object find(String sql, Object[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery(); Object obj = null;
if(rs.next()) {
obj = rowMapper(rs);
}
return obj;
} catch(SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
JdbcUtils.free(rs, ps, conn);
}
} abstract protected Object rowMapper(ResultSet rs) throws SQLException; public int update(String sql, Object[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
return ps.executeUpdate();
} catch(SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
JdbcUtils.free(rs, ps, conn);
}
} }
UserDaoImpl类:
public class UserDaoImpl extends AbstractDao { //①返回一个User对象
public User findUser(String loginName, String password) {
String sql = "select id,name,birthday,money from user where name = ?";
Object[] args = new Object[] { loginName };
Object user = super.find(sql, args);
return (User)user;
}
protected Object rowMapper(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
user.setBirthday(rs.getDate("birthday"));
return user;
}
//②返回一个字符串
public String findUserName(int id) {
String sql = "select name from user where id = ?";
Object[] args = new Object[] { id };
Object user = super.find(sql, args);
return ((User)user).getName();
}
protected Object rowMapper1(ResultSet rs) throws SQLException {
return rs.getString("name");
} public void delete(User user) {
String sql = "delete from user where id = ?";
Object[] args = new Object[] { user.getId() };
super.update(sql, args);
} public void update(User user) {
String sql = "update user set name = ?,birthday = ?,money = ? where id = ?";
Object[] args = new Object[] {user.getName(), user.getBirthday(),
user.getMoney(), user.getId()};
super.update(sql, args);
} }
使用策略模式对模板方法设计模式进行改进
举例说明:
sql1 = "select name from user where id = ?";
sql2 = "select id, name, age from user where id = ?";
这两个sql查询的对象不一样,一个是只需要返回一个name属性的值就可以,而另外一个sql需要返回一个User对象,这样的话,我们上面的模板方法就又不好用了,虽然可以查出来但是性能上有损失 我只要查询一个username,你可能会把整个User对象都给我查询出来。我们可以针对每个不同的sql语句查询的内容的不同把模板方法也分解成多个不一样的能满足相应sql查询语句的方法,这就叫做策略模式(第一次听说),就是针对每一种情况都有不同的方法,来解决。
①先创建一个行映射器,不要以为这些都是废话,这是我们以后理解spring技术之JdbcTemplate的基础。
public interface RowMapper {
public Object mapRow(ResultSet rs) throws SQLException;
}
②创建一个基类,传入行映射器接口。
public class MyDaoTemplate { public Object find(String sql, Object[] args, RowMapper rowMapper) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery(); Object obj = null;
if(rs.next()) {
obj = rowMapper.mapRow(rs);
}
return obj;
} catch(SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
JdbcUtils.free(rs, ps, conn);
}
} }
③创建UserDaoImpl类。
/*
* 策略设计模式,通过类之间的组合来达到
*/
public class UserDaoImpl2 {
MyDaoTemplate template = new MyDaoTemplate(); public User findUser(String loginName, String password) {
String sql = "select id,name,birthday,money from user where name = ?";
Object[] args = new Object[] { loginName };
Object user = this.template.find(sql, args, new UserRowMapper());
return (User)user;
} public String findUserName(int id) {
String sql = "select name from user where id = ?";
Object[] args = new Object[] { id };
/*
匿名内部类
Object name = this.template.find(sql, args, new RowMapper() { @Override
public Object mapRow(ResultSet rs) throws SQLException {
return rs.getString("name");
}
});
*/
Object name = this.template.find(sql, args, new StringRowMapper());
return (String)name;
} }
//不用匿名内部类就老老实实地写个类实现行映射器,即使麻烦
class StringRowMapper implements RowMapper { @Override
public Object mapRow(ResultSet rs) throws SQLException {
return rs.getString("name");
} }
class UserRowMapper implements RowMapper { @Override
public Object mapRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
user.setBirthday(rs.getDate("birthday"));
return user;
} }
接下来我们来说spring之JdbcTemplate技术
使用JdbcTemplate工具类简化对象查询
(1)new RowMapper就是实现一个行映射器,就是对ResultSet的处理,那么内部是一个接口我们在传递参数的时候,可以用匿名类的方式实现,因为sql是我们自己写的,所以ResultSet如何映射有你自己处理。代码如下(代码是不是似曾相见啊!):
static JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource());
static User findUser1(String name) {
String sql = "select id,name,birthday,money from user where name = ?";
Object[] args = new Object[] { name };
/*
* The type org.springframework.dao.DataAccessException cannot be resolved.
* It is indirectly referenced from required .class files
* org.springframework.dao.DataAccessException该类型不存在,不能从请求的.class文件中正确引用,应该是导入包的问题
* 解决方法:导入spring.transaction-3.0.5.jar包或spring-tx-3.2.2.RELEASE.jar包就好了。
*/
Object user = jdbc.queryForObject(sql, args, new RowMapper() { @Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
user.setBirthday(rs.getDate("birthday"));
return user;
} });
return (User)user;
}
第一次写可能会报异常,异常如下:
The type org.springframework.dao.DataAccessException cannot be resolved. It is indirectly referenced from required .class files
中文意思:org.springframework.dao.DataAccessException该类型不存在,不能从请求的.class文件中正确引用,应该是导入包的问题。
解决方法:导入spring.transaction-3.0.5.jar(最新的spring已改名)包或spring-tx-3.2.2.RELEASE.jar(spring-framework-4.2.5.RELEASE)包就好了。
或许还会报异常:
java.lang.ClassNotFoundException: org.springframework.beans.factory.InitializingBean
解决办法同上。
所导jar包(红框中的jar包一定注意导入):
(2)new BeanPropertyRowMapper在创建这个对象的时候需要传递一个.class文件,JdbcTemplete会通过反射技术,把ResultSet中的值取出来封装成一个对象返回回去。代码如下:
static User findUser(String name) {
String sql = "select id,name,birthday,money from user where name = ?";
Object[] args = new Object[] { name };
//queryForObject()只返回一条记录
Object user = jdbc.queryForObject(sql, args, new BeanPropertyRowMapper(User.class));
return (User)user;
}
JdbcTemplate类中的其他各个查询方法
在这一小节中展示了JdbcTemplete中提供的各种方法,下面列举了几个都是前面我们自己写jdbc程序的时候能实现的,只不过jdbcTemplete更进一步的做了更好的封装。代码如下:
/*
* 得到Map类型的结果,如:
* data:{userId=1212, name=dao name123, birthday=2016-03-29, money=20000.1}
*/
static Map getData(int id) {
String sql = "select id as userId,name,birthday,money from user where id = "+id;
return jdbc.queryForMap(sql);
} //根据id查出姓名
static String getUserName(int id) {
String sql = "select name from user where id = "+id;
Object name = jdbc.queryForObject(sql, String.class);
return (String)name;
} //返回用户的总数
static int getUserCount() {
String sql = "select count(*) from user";
Object count = jdbc.queryForObject(sql, Integer.class);
return (Integer)count;
}
使用JdbcTemplate完成数据库修改和其他功能
介绍在JdbcTemplete中update许多方法的使用,其实和其他的都是一样的,就是传递参数、sql语句之类的,在前面的示例中我们做过一个例子就是在保存一个对象之后呢,得到这个对象保存后在数据库的key值,那么在JdbcTemplete中也为我们提供了相应的方法可以实现这一需求。代码如下:
//返回插入记录的id
static int addUser(final User user) {
/*
* 调用execute方法,传递参数的时候也是使用一个匿名类
* 然后要实现一个doInConnection的方法,
* 在这个方法中它会把Connection对象创建好了之后交给你来处理,所以这也就很灵活。 这称为连接回调
*/
jdbc.execute(new ConnectionCallback() {//连接回调, @Override
public Object doInConnection(Connection con) throws SQLException, DataAccessException {
String sql = "insert into user (name,birthday,money) values (?,?,?)";
PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, user.getName());
ps.setDate(2, new java.sql.Date(user.getBirthday().getTime()));
ps.setFloat(3, user.getMoney());
//4.执行语句
ps.executeUpdate(); /*
* 保存之前user没id值
* 保存之后user有id值,即user在保存之前或之后是有区别的!
*/
ResultSet rs = ps.getGeneratedKeys();
if(rs.next())
user.setId(rs.getInt(1));
return null;
} });
return 0;
}
使用支持命名参数的JdbcTemplate
这个支持命名参数的JdbcTemplete其实就是在JdbcTemplete的基础上对参数又进行了一系列的包装,让我们在对sql语句中的参数进行传值的时候更加的方便和好用。代码如下:
public class NamedJdbcTemplate {
//命名参数JdbcTemplate
static NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(
JdbcUtils.getDataSource()); public static void main(String[] args) {
User user = new User();
user.setMoney(10);
user.setId(2); System.out.println(findUser1(user));
} static void addUser(User user) {
/*
* 构建sql语句,把需要传递的参数用名称的方法表示出来 需要注意的是 :
* 如果用这种方式添加一个对象 ,你的values()括号中的代表参数的值必须与User类中的属性是一致的才可以
*/
String sql = "insert into user (name,birthday,money) values (:name,:birthday,:money)";
/*
* 直接把方法参数传递过来的User对象给他,他会通过反射的方式找到sql语句中需要设置值的属性
* 然后给他赋值,这就是为什么要求命名参数要与JavaBean中的属性名称一致的原因
*/
SqlParameterSource pn = new BeanPropertySqlParameterSource(user);
/*
* new keyHodler对象,他可以把添加这条记录后在数据库生成的id返回回来
*/
KeyHolder keyHolder = new GeneratedKeyHolder();
//调用NamedParameterJdbcTemplate的update方法
named.update(sql, pn, keyHolder);
//如果主键是int型 则返回一个int值
int id = keyHolder.getKey().intValue();
user.setId(id); //如果主键是String或者是联合主键我们可以让它返回一个Map对象,然后遍历得到相应的数据
Map map = keyHolder.getKeys(); } /*
* 此方法演示的是把sql语句中需要传递的参数按照名称放到Map中
* 调用方法的时候直接把Map对象传递进去,就可以给sql赋值了,
* 这和针对"?"赋值来说不容易出错,因为如果采用"?"作为占位符然后赋值 顺序必须要正确否则就会出错。
*/
static User findUser(User user) {
String sql = "select id,name,birthday,money from user "
+ "where money > :m and id < :id";
Map params = new HashMap();
//params.put("n", user.getName());
params.put("m", user.getMoney());
params.put("id", user.getId());
//queryForObject()只返回一条记录
Object u = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class)); /*
String sql = "select id,name,birthday,money from user "
+ "where name = ? and money = > ? and id < ?";
Object[] args = new Object[] { user.getName(), user.getMoney(), user.getId() };
*/
return (User)u;
} static User findUser1(User user) {
/*
* 构建sql语句,用相应的名称作为占位符:
* 注意与Javabean中属性名称一样
*/
String sql = "select id,name,birthday,money from user "
+ "where money > :money and id < :id";//名字限制为类的属性名
//直接把User对象传递给他,他会自动找到sql中需要赋值的参数并且赋值
SqlParameterSource pn = new BeanPropertySqlParameterSource(user);
/*
* 调用方法执行sql
* new BeanPropertyRowMapper我们把相应的.class文件传递进去,
* 他会把sql语句执行的结果封装成相应的对象
*/
Object u = named.queryForObject(sql, pn, new BeanPropertyRowMapper(User.class));
return (User)u;
} }
findUser1(user)时可能会报异常如下:
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 2
原因:从数据库里查出两条记录,而此方法期望返回一条记录,冲突出错。
spring-framework-4.2.5.RELEASE已经废弃SimpleJdbcDaoSupport及SimpleJdbcTemplate,所以在此不赘述了SimpleJdbcTemplate了,李勇老师那个时代可能还在。
使用JdbcTemplate实现DAO和用工厂灵活切换实现
用我们前面学习过的Jdbctemplate或NamedParameterJdbcTemplate来重新实现前面例子中写的Userdao这个接口,代码会简洁很多。代码如下(亲测可用):
只须在配置文件(daoconfig.properties)中修改如下:
public class UserDaoSpringImpl implements UserDao { private NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(
JdbcUtils.getDataSource()); @Override
public void addUser(User user) {
String sql = "insert into user (name,birthday,money) values (:name,:birthday,:money)";
SqlParameterSource pn = new BeanPropertySqlParameterSource(user);
KeyHolder keyHolder = new GeneratedKeyHolder();
named.update(sql, pn, keyHolder);
int id = keyHolder.getKey().intValue();
user.setId(id);
} @Override
public User getUser(int userId) {
String sql = "select id,name,birthday,money from user where id = :id";
Map params = new HashMap();
params.put("id", userId);
Object user = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
return (User) user;
} @Override
public User findUser(String loginName, String password) {
String sql = "select id,name,birthday,money from user where name = :name";
Map params = new HashMap();
params.put("name", loginName);
Object user = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class));
return (User)user;
} @Override
public void update(User user) {
String sql = "update user set name = :name,birthday = :birthday,money = :money where id = :id";
SqlParameterSource params = new BeanPropertySqlParameterSource(user);
named.update(sql, params);
} @Override
public void delete(User user) {
String sql = "delete from user where id = :id";
Map params = new HashMap();
params.put("id", user.getId());
named.update(sql, params);
}
JDBC第四次学习的更多相关文章
- JDBC 学习笔记(二)—— 详解 JDBC 的四种驱动类型
JDBC 有四种驱动类型,分别是: JDBC-ODBC 桥(JDBC-ODBC bridge driver plus ODBC driver) 本地 API 驱动(Native-API partly ...
- apue第四章学习总结
apue第四章学习总结 4.1.若以stat函数去替换lstat函数,会发生: 原来的目录路径: $:~/workspace/apue2/include$ ls -l apue.h abc lrwxr ...
- JDBC第三次学习
这是我的JDBC第三次学习了,在学习的过程中,老是会忘掉一些知识,不记下笔记实在不行啊! 使用JDBC调用存储过程 (1)关于如何使用Navicat(11.1.13) for MySQL如何创建存储过 ...
- JDBC操作数据库的学习(2)
在上一篇博客<JDBC操作数据库的学习(1)>中通过对例1,我们已经学习了一个Java应用如何在程序中通过JDBC操作数据库的步骤流程,当然我们也说过这样的例子是无法在实际开发中使用的,本 ...
- Factorization Machines 学习笔记(四)学习算法
近期学习了一种叫做 Factorization Machines(简称 FM)的算法.它可对随意的实值向量进行预測.其主要长处包含: 1) 可用于高度稀疏数据场景:2) 具有线性的计算复杂度.本文 ...
- 《Linux内核设计与实现》第四章学习笔记
<Linux内核设计与实现>第四章学习笔记 ——进程调度 姓名:王玮怡 学号:20135116 一.多任务 1.多任务操作系统的含义 多任务操作系统就是能同时并发地交 ...
- 《Linux内核设计与实现》第四章学习笔记——进程调度
<Linux内核设计与实现>第四章学习笔记——进程调 ...
- Spring实战第四章学习笔记————面向切面的Spring
Spring实战第四章学习笔记----面向切面的Spring 什么是面向切面的编程 我们把影响应用多处的功能描述为横切关注点.比如安全就是一个横切关注点,应用中许多方法都会涉及安全规则.而切面可以帮我 ...
- 孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3
孤荷凌寒自学python第六十四天学习mongoDB的基本操作并进行简单封装3 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十天. 今天继续学习mongoDB的简单操作, ...
随机推荐
- 你必须懂的 T4 模板:深入浅出
示例代码:示例代码__你必须懂的T4模板:浅入深出.rar (一)什么是T4模板? T4,即4个T开头的英文字母组合:Text Template Transformation Toolkit. T4文 ...
- MongoDB学习笔记-数据格式及数据类型
JSON JSON是一种简单的数据表示方式,它易于理解.易于解析.易于记忆.但从另一方面来说,因为只有null.布尔.数字.字符串.数组和对象这几种数据类型,所以JSON有一定局限性.例如,JSON没 ...
- 16.如何设置Quartus II Programmer,保护pof不被读出
Program时,把security bit勾上,点击start 这样examine时就不能正确的读出pof 读出来的pof 除文件头外,其余的内容全为0 怎么样,大家试试!
- Min Stack
Min Stack Design a stack that supports push, pop, top, and retrieving the minimum element in constan ...
- ThinkPHP运算符与PHP运算符对照表
ThinkPHP运算符与PHP运算符对照表 ThinkPHP标签 说明及对应PHP标签 备注 eq 等于(=)(==:用于模板判断时) 可用于查询条件与模板判断 neq 不等于(!=) 可用于查询条件 ...
- android开发 实现同时显示png/jpg 等bitmap图片还可以显示gif图片,有效防止OOM
本来使用第三方jar包 GifView.jar 发现使用的时候不能显示png图片,而且多次setgifimage的时候还会OOM: 现在使用了一个新的第三方,demo是别人的, 下载链接:http: ...
- Netsharp快速入门(之9) 基础档案(工作区3 添加商品菜单,以及在产品中打开商品界面)
作者:秋时 杨昶 时间:2014-02-15 转载须说明出处 3.5.2 添加导航菜单 1.打开平台工具,插件和资源节点,选择创建导航菜单,打开创建向导 2.选择所属插件 3.选择在哪个分类下 ...
- [noip2005提高]过河 dp
由于L的范围到了109,用普通dp做肯定是不成了: 可以观察到M的数量很小,dp在转移的过程中有大量的无用转移: 可以想到压缩范围,问题是如何压缩,观察若S=9,T=10时,能到达的点,9,10,18 ...
- 【转载】在程序中动态改变static text控件的caption值
方法1,给STATIC控件取个名字叫IDC_STATICTITLE 然后在ClassWizard中设定一个控件变量给它叫m_statictitle 然后用m_statictitle.SetWindow ...
- PHP 扩展库
表 6.1. PHP 扩展库 扩展库 说明 注解 php_bz2.dll bzip2 压缩函数库 无 php_calendar.dll 历法转换函数库 自 PHP 4.0.3 起内置 php_cpdf ...