Class.forname(DriverName)
可以向 DriverManager 注册一个驱动程序。然后通过 DriverManager 的 getConnection(DB_URL, USER, PASS)
方法就可以调用该驱动程序,建立到后端数据库的物理连接。 常用的JDBC URL 格式
* MySQL:jdbc:mysql://
: /database
* ORACLE:jdbc:oracle:thin:@
: :database
* MicroSoft SQL Server:jdbc:microsoft:sqlserver://
: ;DatabaseName=database
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery('select * from table');
stmt.execute('...');
stmt.executeUpdate('...');
DB_URL
中增加 useCursorFetch=true
参数 jdbc:mysql://:/?useCursorFetch=true
PreparedStatement
来代替 Statement
。 相比 Statement
, PreparedStatement
要求在生成PreparedStatement
对象时就要传入 SQL 语句。 该 SQL 语句是个参数格式化的语句,其 where
的条件都是通过 ?
的形式来表示的, 后续通过 PreparedStatement
的 setString
和 setInt
方法来设置参数。 通过setFetchSize
来设置客户端JDBC每次从服务端取回的记录的数量。由于字段内容过大,可能会造成JVM内存溢出
解决方案 流方式:以二进制流的方式将大字段划分为多个区间逐个读取
// 获取对象流
InputStream in = rs.getBinaryStream("字段名称");
// 将对象流写入文件
File f = new File(FILE_URL);
OutputStream out = null;
out = new FileOutputStream(f);
int temp = 0;
while ((temp = in.read()) != -1) {
out.write(temp);
}
in.close();
out.close();
利用循环执行 stmt.executeUpdate 来进行插入海量数据会十分缓慢
解决方案 批处理: 通过发送一次 SQL 可以处理多条数据
通过 Statement 的 addBatch()
executeBatch()
以及 clearBatch()
来实现批处理
public class DBPoolTest {
public static BasicDataSource ds = null;
public final static String DRIVER_NAME = "com.mysql.jdbc.Driver";
public final static String USER_NAME = "root";
public final static String PASSWORD = "***";
public final static String DB_URL = "jdbc:mysql://localhost/database";
public static void dbpoolInit() {
// 创建数据库连接池
ds = new BasicDataSource();
// 配置数据库连接池
ds.setUrl(DB_URL);
ds.setDriverClassName(DRIVER_NAME);
ds.setPassword(PASSWORD);
ds.setUsername(USER_NAME);
}
public void dbPoolTest() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 租借数据库连接
conn = ds.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from users");
while (rs.next()) {
System.out.println(rs.getString("userName"));
}
} catch (SQLException e) {
} finally {
// 归还数据库连接
try {
if (conn != null)
conn.close();
if (stmt != null)
stmt.close();
if (rs != null)
rs.close();
} catch (SQLException e) {
}
}
}
public static void main(String[] args) {
dbpoolInit();
new DBPoolTest().dbPoolTest();
}
}
BasicDataSource
的 setInitialSize()
方法在应用程序启动时在连接池中预置一定数量的连接。BasicDataSource
的 setMaxTotal
方法可以设置最大连接数,当达到最大连接数时,后续连接请求进入等待队列。起到限流保护数据库的作用。BasicDataSource
的 setMaxWaitMillis
方法设置请求最大等待时间,超过该时间则会抛出异常。BasicDataSource
的 setMaxIdle
方法可以设置最大空闲连接数,当空闲连接超过该值时数据库连接池会销毁多余连接。可以减少后端数据库不必要的资源的损耗BasicDataSource
的 setMinIdle
方法可以保证连接池有足够的连接可以被租借DBCP 定期检查,确保数据库连接池中的连接都是有效的
6. 通过 BasicDataSource
的 setTestWhileIdle()
方法可以开启定期检查功能
7. 通过 BasicDataSource
的 setMinEvictableldleTimeMillis()
方法设置销毁连接的最小空闲时间(最好小于服务器端数据库自动关闭连接的阈值时间)
8. 通过 BasicDataSource
的 setTimeBtweenEvictionRunsMillis()
方法设置检查运行时间的时间间隔
web应用架构下,客户端用户无法直接访问数据库,必须通过发送http请求到服务器,由服务器访问后端数据库。SQL注入利用应用业务程序漏洞,伪装自己的请求,欺骗业务程序达到获取数据库数据的目的
SQL 注入就是用户在输入表单或URL参数中输入SQL命令达到欺骗服务器的目的,破坏原有的SQL语义,发送恶意的SQL语句到后端数据库,导致数据库信息泄露。
实例代码:
// 检索登录用户名与密码
String sql = "select * from user where userName = '" + userName + "' and password = '" password + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
此时如果用户传入的用户名为 ZhangSan';--
密码随便输入,此时程序中设定的SQL语句就会变为两句,并且由于--
这个SQL注释符的原因,后一句SQL语句被注释掉。最终就变为了select * from user where userName = 'ZhangSan';
攻击者不需要密码就可以成功登录。
不要使用动态拼接的SQL语句,因为这样SQL语句的语义在运行前是不确定的。可以使用格式化的SQL语句,先确定SQL的语义再传入参数。
String sql = "select * from users where userName = ? and password = ?";
PreparedStatement ptmt = conn.prepareStatement(sql);
ptmt.setString(1, userName);
ptmt.setString(2, password);
ptmt.executeQuery();
事务(Transaction)是并发控制的基本单位,指作为单个逻辑工作单元执行的一系列操作,而这些逻辑工作单元需要满足ACID特性。
ACID特性:原子性,一致性,隔离性,持久性
开启事务: connection.setAutoCommit(false);
此后该connection的所有sql语句都会作为JDBC的一个事务来执行.
提交事务: connection.commit();
回滚事务: connection.rollback();
持久化类与数据库表之间的映射关系
对持久化对象的操作自动转换成对关系数据库的操作
如何映射?
* 关系数据库的每一行映射为一个对象
* 关系数据库的每一列映射为对象的一个属性
前身是 apache 基金会下的一个开源项目 iBatis
是一个支持自定义SQL、存储过程和高级映射的持久化框架
使用XML或者注解配置
能够映射基本数据元素、接口、Java对象到数据库
SQLID
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="jdbc">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver">property>
<property name="url" value="jdbc:mysql://databaseURL">property>
<property name="username" value="username">property>
<property name="password" value="password">property>
dataSource>
environment>
environments>
<mappers>
<mapper resource="映射关系配置文件的路径" />
mappers>
configuration>
// 构造对象
public class User {
private int id;
private String userName;
private String corp;
public User(Integer id, String userName, String corp) {
this.id = id;
this.userName = userName;
this.corp = corp;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
// 构造接口
public interface GetUserInfo {
public User getUser(int id);
public void addUser(User user);
public void updateUser(User user);
public void deleteUser(User user);
}
<mapper namespace="package.GetUserInfo">
<select id="getUser" parameterType="int" resultType="package.User">
select id, userName, corp from user where id =#{id}
select>
mapper>
流程:加载配置文件 -> 生成 SqlSessionFactory -> 获取 SqlSession -> Session 执行 SQL
//1. 声明配置文件目录位置
String resource = "conf.xml";
//2. 加载应用配置文件
InputStream is = HelloMyBatis.class.getClassLoader().getResourceAsStream(resource);
//3. 创建SqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
//4. 获取Session
SqlSession session = sessionFactory.openSession();
try {
//5. 获取操作类
GetUserInfo getUserInfo = session.getMapper(GetUserInfo.class);
//6. 完成查询操作
User user = getUserInfo.getUser(1);
System.out.println(user.getId() + " " + user.getUserName() + " " + user.getCorp());
} finally {
//7. 关闭Session
session.close();
}
优势: 入门门槛较低、更加灵活、SQL优化
劣势: 需要自己编写SQL,工作量大、数据库移植性差
public interface GetUserInfoAnnotation {
@Select("select * from users where id = #{id}")
public User getUser(int id);
}
ResultMap 元素是 MyBatis 中最重要最强大的元素。可以用于实现复杂查询结果到复杂对象关联关系的转化
以选课系统为例,数据库应该有四张表
1. users: id, userName, corp
2. userCourse: id, userId, courseId
3. course: id, courseName, teacherId
4. teachers: id, teacherName
// 定义 User 类
public class User {
private int id;
private String userName;
private String corp;
private List courses;
public User(Integer id, String userName, String corp) {
this.id = id;
this.userName = userName;
this.corp = corp;
}
// 此处省略了 getter 和 setter没有写出
}
// 定义 Course 类
public class Course {
private int id;
private String courseName;
private Teacher teacher;
}
// 定义 Teacher 类
public class Teacher {
private int id;
private String teacherName;
}
<mapper namespace="net.gyronee.mybatis_resultMap.UserOp">
<select id="getUser" parameterType="int"
resultMap="UserMap">
select u.id as userId, userName, courseName, corp, c.id as courseId, teacherName
from users u left join userCourse uc on u.id = uc.userId left join
course c on c.id = uc.courseId left join teachers on teachers.id = c.id
where u.id = #{id}
select>
<resultMap type="net.gyronee.mybatis_resultMap.User" id="UserMap">
<constructor>
<idArg column="userId" javaType="int"/>
<arg column="userName" javaType="String"/>
<arg column="corp" javaType="String"/>
constructor>
<collection property="courses" ofType="net.gyronee.mybatis_resultMap.Course">
<id property="id" column="courseId"/>
<result property="courseName" column="courseName"/>
<association property="teacher" column="teacherId"
javaType="net.gyronee.mybatis_resultMap.Teacher">
<id property="id" column="teacherId"/>
<result property="teacherName" column="teacherName"/>
association>
collection>
resultMap>
mapper>