Workbench 또는 MySQL CLI 에서 다음 명령어로 실습에 필요한 schema, table, data 를 만든다.
CREATE SCHEMA `de-jdbc`;
USE `de-jdbc`;
CREATE TABLE `product` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`updated_at` datetime NOT NULL,
`contents` varchar(2048) COLLATE utf8mb4_unicode_ci NOT NULL,
`price` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes1', '2022-08-01 01:00:00', 'This is shoes1', '10000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes2', '2022-08-01 02:00:00', 'This is shoes2', '20000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes3', '2022-08-01 03:00:00', 'This is shoes3', '30000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes4', '2022-08-01 04:00:00', 'This is shoes4', '40000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes5', '2022-08-01 05:00:00', 'This is shoes5', '50000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shoes6', '2022-08-01 06:00:00', 'This is shoes6', '60000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('backpack', '2022-08-02 04:00:00', 'This is backpack', '15000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('shirt', '2022-08-03 05:00:00', 'This is shirt', '20000');
INSERT INTO `product` (`name`, `updated_at`, `contents`, `price`) VALUES ('glasses', '2022-08-04 06:00:00', 'This is glasses', '10000');
build.gradle 의 dependencies 에 mysql connnetor library를 추가한다.
dependencies {
implementation 'mysql:mysql-connector-java:8.0.30'
}
다음 코드를 따라서 수행해보고, JDBC의 동작을 위해서는 무엇이 필요한지 추측해보자.
try {
//here de-jdbc is database name, root is username and password is null. Fix them for your database settings.
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from product");
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " "
+ rs.getDate(3) + " " + rs.getString(4)
+ " " + rs.getInt(5));
}
con.close();
} catch (Exception e) {System.out.println(e);}
User와 Driver 사이의 인터페이스를 담당. Driver의 이용가능 여부, Database와 Driver사이의 Connection을 관리한다. Driver의 등록/해제, Connection의 연결/해제를 관리한다. Driver의 Register가 반드시 성공해야 DB와 상호작용을 할 수 있다.
과거에는 클래스로딩을 위해서 Class.forName()
으로 해당 클래스를 클래스패스에서 찾아서 클래스로더가 로딩할 수 있도록 한다. 이렇게 클래스 로딩이 되어야 DriverManager가 표준인터페이스로 메소드를 호출할 때, 해당 JDBC드라이버의 구현체로 연결될 수 있었다.
하지만 최신 버전에서는 Java SPI(서비스 프로바이더 인터페이스)를 이용해서 자동으로 MySQL Driver 클래스를 표준 Driver 인터페이스에 등록 해줄 수 있게되었다.
Connection url에 mysql 정보가 있고, myql connector library 가 클래스패스에 있다면, get connection 단계에서 자동으로 찾아준다.
Connection con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/de-jdbc", "root", null);
null : password
DB와의 모든 동작은 getConnection 을 통해서 JDBC Driver 클래스를 찾고, 연결까지 성공한 뒤에 할 수 있다.
Connection 객체 하나는, DB와 Java Client 사이의 하나의 물리적인 연결을 의미한다.
이 연결은 DB입장에서는 하나의 Session을 의미한다. Session은 변경사항의 묶음을 관리하는 하나의 단위라고 이해하면 된다. Session에 대해 자세한 내용은 이후 Transaction, RDBMS이해하기 부분에서 더 다룬다.
해당 연결에서 사용할 Statement, PreparedStatement, and DatabaseMetaData 객체들을 얻어올 수 있다.
Statement createStatement()
PreparedStatement prepareStatement(String sql)
DatabaseMetaData getMetaData()
Session이기 때문에 Session단위에서 이루어지는 동작을 메소드로 제공한다. 이 메소드들은 Transaction 실습에서 구체적으로 다룬다.
setAutoCommit(boolean status)
true
. Auto Commit을 사용한다면, executeXXX 종류의 함수를 실행하면 바로 commit이 발생해서 변경사항이 최종 테이블에 반영이 된다.commit()
rollback()
void setTransactionIsolation(int level)
close()
Statement를 이용해서 실제 수행할 쿼리를 작성할 수 있다. 수행 결과를 ResultSet 객체로 받아온다. 정적인 쿼리를 작성한다.
ResultSet [executeQuery](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#executeQuery-java.lang.String-) (String sql)
int [executeUpdate](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#executeUpdate-java.lang.String-) (String sql)
boolean [execute](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#execute-java.lang.String-) (String sql)
[getResultSet](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#getResultSet--)()
함수를 호출해서 결과를 얻는다.[getUpdateCount](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#getUpdateCount--)()
를 호출해서 결과를 얻는다.-1
은 현재 값이 없거나, ResultSet 인 경우이다.[getMoreResults](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#getMoreResults--)()
함수로 execute()의 리턴값과 같은 방식으로 해석한다.((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
int[] [executeBatch](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#executeBatch--) ()
- addBatch(String sql) 로 쌓인 SQL을 배치로 수행한다. 모든 커맨드가 성공하면 update된 count 를 배열로 리턴한다.
void [addBatch](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#addBatch-java.lang.String-) (String sql)
- 현재 Statement 객체에 배치로 실행할 SQL 명령어를 추가한다. 추가만 할 뿐, 실행하지는 않는다.
위 함수말고도 많은 기능을 지원한다. Statement 클래스에는 실제 DB와의 동작을 수행할 수 있는 함수들이 제공되므로 Statement 클래스의 Javadoc을 한 번 쯤은 정독해두는 것을 추천한다. Java에서 DB를 이용하는 거의 모든 기능을 이 기능을 토대로 만든다고 생각하면
된다.
Main.java
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
try {
// here de-jdbc is database name, root is username and password is null. Fix them for your
// database settings.
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select * from product");
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getDate(3) + " "
+ rs.getString(4) + " " + rs.getInt(5));
}
con.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
try {
// here de-jdbc is database name, root is username and password is null. Fix them for your
// database settings.
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
int result = stmt.executeUpdate("UPDATE product SET `price` = `price`+10000 where `id` = 1");
System.out.println("result of update one record: " + result);
int multiResult = stmt
.executeUpdate("UPDATE product SET `price` = `price`-1000 where `name` like 'shoes%'");
System.out.println("result of update multiple record: " + multiResult);
int noResult =
stmt.executeUpdate("UPDATE product SET `price` = `price`+10000 where `id` = 999999");
System.out.println("result of update nothing: " + noResult);
con.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
try {
// here de-jdbc is database name, root is username and password is null. Fix them for your
// database settings.
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
boolean selectResult = stmt.execute(
"select `id`, `name`, `updated_at`, `contents`, `price` from product where id between 1 and 5;");
if (selectResult) {
ResultSet resultSet = stmt.getResultSet();
while (resultSet.next()) {
printRs(resultSet);
}
}
boolean updateResult =
stmt.execute("UPDATE product SET `price` = `price`+10000 where `id` = 1;");
if (!updateResult) {
System.out.println("result of update: " + stmt.getUpdateCount());
}
con.close();
} catch (Exception e) {
System.out.println(e);
}
}
private static void printRs(ResultSet rs) throws SQLException {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getDate(3) + " "
+ rs.getString(4) + " " + rs.getInt(5));
}
}
Main.java
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
try {
//here de-jdbc is database name, root is username and password is null. Fix them for your database settings.
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
stmt.addBatch(
"select `id`, `name`, `updated_at`, `contents`, `price` from product where id between 1 and 5;");
stmt.addBatch("UPDATE product SET `price` = `price`+10000 where `id` = 1;");
try {
int[] results = stmt.executeBatch();
} catch(BatchUpdateException batchUpdateException({
System.out.println(batchUpdateException.getMessage());
}
stmt.addBatch("UPDATE product SET `price` = `price`+10000 where `id` = 1;");
stmt.addBatch("UPDATE product SET `price` = `price`+10000 where `id` = 2;");
stmt.addBatch("UPDATE product SET `price` = `price`+10000 where `id` between 3 and 5;");
int[] results = stmt.executeBatch();
for (int result : results) {
if (result >= 0) {
System.out.println("result of update: " + result);
}
}
con.close();
} catch (Exception e) {System.out.println(e);}
}
private static void printRs(ResultSet rs) throws SQLException {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getDate(3) + " "
+ rs.getString(4) + " " + rs.getInt(5));
}
}
ResultSet은 쿼리의 결과를 cursor를 이용해서 다룰 수 있도록 하는 객체이다.
ResultSet은 쿼리의 결과를 테이블 형태로 상정하고, 특정 row(행)을 가리키고 있는 cursor를 가진다. 쿼리의 결과가 단건일수도, 여러건
일수도 있다. 결과 전체를 리스트나 배열로 주면 되지 않냐고 생각할 수도 있다. 하지만, 쿼리 결과가 많다면 그 결과를 한 번에 어플리케이
션의 메모리에 모두 로드할 수 없을 수도 있기 때문에 cursor를 사용한다.
cursor는 방향성이 있다. 시작은 첫 번째 row의 이전을 가리키고 있다. 커서가 가리키는 내용은 업데이트가 불가능하다.
단, 설정으로 방향을 반대로 바꿀 수도 있고, 내용이 업데이트 가능하게 만들 수도 있다.
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
커서의 동작을 그림으로 표현하면 다음과 같다.
ResultSet의 주요함수는 다음과 같다. Javadoc
public boolean next()
public boolean previous()
public boolean first()
public boolean last()
public boolean absolute(int row)
public boolean relative(int row)
public int getInt(int columnIndex)
public int getInt(String columnName)
public String getString(int columnIndex)
public String getString(String columnName)
public Blob getBlob(int columnIndex)
public Blob getBlob(String columnName)
public Clob getClob(int columnIndex)
public Clob getClob(String columnName)
ResultSet은 결과물을 데이터로 가져오려면 꼭 사용해야하는 인터페이스이다. 이것도 역시 ResultSet의Javadoc을 한 번 읽어보면 앞으로 JDBC를 이용해서 데이터를 가져올 때 어떤식으로 활용할 수 있을지 스스로 고민할 수 있을 것이다.
Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
try {
// here de-jdbc is database name, root is username and password is null. Fix them for your
// database settings.
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(
"select `id`, `name`, `updated_at`, `contents`, `price` from product where id between 1 and 5 order by id asc");
// cursor init
if (rs.next()) {
// cursor 1
printRs(rs);
}
if (rs.last()) {
// cursor 5
printRs(rs);
}
if (!rs.next()) {
// end of result set
try {
printRs(rs);
} catch (SQLException sqlException) {
System.out.println(sqlException.getErrorCode() + ", " + sqlException.getMessage());
}
}
rs.last();
if (rs.previous()) {
// cursor 4
printRs(rs);
}
if (rs.absolute(2)) {
// cursor 2
printRs(rs);
}
if (rs.relative(2)) {
// cursor 4
printRs(rs);
}
if (rs.relative(-2)) {
// cursor 2
printRs(rs);
}
if (rs.first()) {
// cursor 1
printRs(rs);
}
if (!rs.previous()) {
// Before start of result set
try {
printRs(rs);
} catch (SQLException sqlException) {
System.out.println(sqlException.getErrorCode() + ", " + sqlException.getMessage());
}
}
if (!rs.previous()) {
// Before start of result set
try {
printRs(rs);
} catch (SQLException sqlException) {
System.out.println(sqlException.getErrorCode() + ", " + sqlException.getMessage());
}
}
con.close();
} catch (Exception e) {
System.out.println(e);
}
}
private static void printRs(ResultSet rs) throws SQLException {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getDate(3) + " "
+ rs.getString(4) + " " + rs.getInt(5));
}
}
자바는 객체지향 언어이다. 매번 코드로 SQL의 쿼리 결과의 몇 번째 값이 어떤 타입의 데이터이다는 것을 코드로 짜려면 시간도 오래걸릴 뿐더러, 작성자가 아닌 다른 사람이 코드를 읽기도 불편하다.
ResultSet 의 결과를 Java Object로 만들면 타입 안정성과 객체지향 프로그래밍의 장점을 살려서 프로그램을 간결하게 만들 수 있다.
Product.java
package org.de.jdbc.mapper;
import java.time.LocalDateTime;
public class Product {
int id;
String name;
LocalDateTime updated_at;
String contents;
int price;
public Product(int id, String name, LocalDateTime updated_at, String contents, int price) {
this.id = id;
this.name = name;
this.updated_at = updated_at;
this.contents = contents;
this.price = price;
}
@Override
public String toString() {
return "Product{" + "id=" + id + ", name='" + name + '\'' + ", updated_at=" + updated_at
+ ", contents='" + contents + '\'' + ", price=" + price + '}';
}
}
ResultSetMapper.java
package org.de.jdbc.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ResultSetMapper {
public static Product create(ResultSet rs) throws SQLException {
return new Product(rs.getInt(1), rs.getString(2), rs.getTimestamp(3).toLocalDateTime(),
rs.getString(4), rs.getInt(5));
}
}
Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import org.de.jdbc.mappe.Product;
import org.de.jdbc.mappe.ResultSetMapper;
public class Main {
public static void main(String[] args) throws SQLException {
// here de-jdbc is database name, root is username and password is null. Fix them for your
// database settings.
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"select `id`, `name`, `updated_at`, `contents`, `price` from product");
while (rs.next()) {
System.out.println(ResultSetMapper.create(rs));
}
con.close();
}
}
하지만 이런 방식은, 내가 수행하는 SELECT문의 모든 결과를 별도의 Java Class로 선언해놓아야 가능하다. 그리고 데이터를 변환하는 함수도 SELECT문의 결과에 따라 각각 만들어놓아야 한다. 실제로 업무에서 활용하려면 이런 코드를 짜는데 시간이 굉장히 많이 들어간다. 그리고 ResultSet은 index별로 타입을 구분해서 함수를 호출해야하므로 쿼리나 코드의 변경에도 버그가 발생할 확률이 높아진다. 즉, 변경에 취약하다.
이런 ResultSet과 POJO class 를 매핑하는 코드를 매번 짜는 것이 불편해서 만들어진 기술(Library)을 ORM(Object-Relational Mapping)이다. 이름에서 추론할 수 있는 바와 같이, 객체와 Relational Model(관계형 데이터베이스 모델)을 매핑할 수 있는 기능을 가지고 있다. 하나의 테이블이 하나의 Java Class에 해당하고, FK와 같은 부가적인 기능은 함수로 제공한다.
Statement의 subinterface 이다. parameterized query를 사용할 때 쓴다. 변수를 사용할 수 있어서 Statement 종류 중에 가장 활용도가 높다.
여러 종류의 쿼리를 사용하지만, 컴파일은 한 번만 하면 되므로 성능상 이점이 있다.
쿼리에서 parameterized 하고 싶은 부분을 ? (물음표)로 작성하고, 해당 부분에 들어갈 파라미터는 PreparedStatement 의 함수를 이용해서 세팅한다.
"insert into table value(?, ?)"
PreparedStatement에는 Statement의 인터페이스를 모두 구현하고, 아래와 같은 함수들이 추가되었다. Javadoc을 보면 더 많은 함수를 확인할 수 있다.
public void setInt(int paramIndex, int value)
public void setString(int paramIndex, String value)
public void setFloat(int paramIndex, float value)
public void setDouble(int paramIndex, double value)
Product.java
package org.de.jdbc.mapper;
import java.time.LocalDateTime;
public class Product {
int id;
String name;
LocalDateTime updated_at;
String contents;
int price;
public Product(int id, String name, LocalDateTime updated_at, String contents, int price) {
this.id = id;
this.name = name;
this.updated_at = updated_at;
this.contents = contents;
this.price = price;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public LocalDateTime getUpdated_at() {
return updated_at;
}
public void setUpdated_at(LocalDateTime updated_at) {
this.updated_at = updated_at;
}
public String getContents() {
return contents;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Product{" + "id=" + id + ", name='" + name + '\'' + ", updated_at=" + updated_at
+ ", contents='" + contents + '\'' + ", price=" + price + '}';
}
}
ResultSetMapper
package org.de.jdbc.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ResultSetMapper {
public static Product create(ResultSet rs) throws SQLException {
return new Product(rs.getInt(1), rs.getString(2), rs.getTimestamp(3).toLocalDateTime(),
rs.getString(4), rs.getInt(5));
}
public static void printRs(ResultSet rs) throws SQLException {
System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getDate(3) + " "
+ rs.getString(4) + " " + rs.getInt(5));
}
}
Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.de.jdbc.mapper.Product; // can be different package name
import org.de.jdbc.mapper.ResultSetMapper; // can be different package name
public class Main {
public static void main(String[] args) throws SQLException {
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
PreparedStatement psUpdate =
con.prepareStatement("UPDATE product SET `price` = price+? where `id` = ?;");
psUpdate.setInt(1, 10000); // price + a
psUpdate.setInt(2, 1); // id
int updatedResult = psUpdate.executeUpdate();
System.out.println("result of update: " + updatedResult);
PreparedStatement psSelect = con.prepareStatement(
"select `id`, `name`, `updated_at`, `contents`, `price` from product where id between ? and ?;");
psSelect.setInt(1, 1);
psSelect.setInt(2, 5);
ResultSet rs = psSelect.executeQuery();
while (rs.next()) {
ResultSetMapper.printRs(rs);
}
psSelect.setInt(1, 6);
psSelect.setInt(2, 10);
ResultSet rs2 = psSelect.executeQuery();
List<Product> productList = new ArrayList<>();
while (rs2.next()) {
productList.add(ResultSetMapper.create(rs2));
}
PreparedStatement psUpdateProduct = con.prepareStatement(
"UPDATE product SET `id` = ?, `name` = ?, `updated_at` = ?, `contents` = ?, price = ? where id = ?");
for (Product product : productList) {
product.setPrice(product.getPrice() - 1000);
product.setUpdated_at(LocalDateTime.now());
psUpdateProduct.setInt(1, product.getId());
psUpdateProduct.setString(2, product.getName());
psUpdateProduct.setTimestamp(3, Timestamp.valueOf(product.getUpdated_at()));
psUpdateProduct.setString(4, product.getContents());
psUpdateProduct.setInt(5, product.getPrice());
psUpdateProduct.setInt(6, product.getId());
psUpdateProduct.addBatch();
psUpdateProduct.clearParameters();
}
int[] results = psUpdateProduct.executeBatch();
for (int result : results) {
System.out.println("result of update: " + result);
}
}
}
DB에는 SQL 말고도 stored procedure 또는 function이 있다. 이것들은 다음과 같은 경우에 사용한다.
Stored Procedure는 SQL을 여러 개 써서 구성할 수 있다는 게 특징이고,
Function은 그 자체로도 호출할 수 있지만, SQL문 내에서도 호출할 수 있다는 점이 Stored Procedure와의 차이점이다.
Function의 사례
Stored Producedure의 사례
JDBC에서는 이런 stored procedure 또는 function은 CallableStatement 클래스를 이용해서 다룬다.
PROCEDURE 정의하기
DELIMITER //sql
CREATE PROCEDURE discount_price(IN _id int unsigned, IN percent int, OUT discounted_price int)
BEGIN
update product set price = (price - (price * (percent/100))) where `id` = _id;
select price into discounted_price from product where `id` = _id;
END //
DELIMITER ;
PRDECURE 삭제하기
DROP PROCEDURE discount_price
PROCEDURE 호출하기
call discount_price(1, 10, @discounted_price);
select @discounted_price;
Connection.prepareCall(String statement)
: CallableStatement를 만든다. 파라미터는 ?로 처리한다.CallalbleStatement
를 사용하는 방법은 PreparedStatement를 사용하는 방법과 같은 방식으로 index/label 별로 타입에 맞는 값을 세팅할 수 있다..CallalbleStatement.registerOutParameter(int index 또는 String label, java.sql.Type type)
로 세팅해야한다.execute()
함수로 실행한다.excute()
함수를 해석하는 방식과 같다.getXXX
함수를 통해서 얻을 수 있다.Main.java
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;
public class Main {
public static void main(String[] args) throws SQLException {
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
CallableStatement stmtProcedureCall = con.prepareCall("call discount_price(?, ?, ?);");
stmtProcedureCall.setInt(1, 1);
stmtProcedureCall.setInt(2, 10);
stmtProcedureCall.registerOutParameter(3, Types.INTEGER);
boolean result = stmtProcedureCall.execute();
System.out.println("result: " + result);
System.out.println("param: " + stmtProcedureCall.getInt(3));
if (!result) {
System.out.println("update count: " + stmtProcedureCall.getUpdateCount());
}
}
}
FUNCTION 정의하기
CREATE FUNCTION add_event_prefix (s CHAR(20))
RETURNS CHAR(50) DETERMINISTIC
RETURN CONCAT('[특별 할인 이벤트] , ',s);
예를 들어 어떤 상품의 이름에 ~특가 블랙프라이데이 특가 이런 경우 갑자기 모든 상품 앞에 ~특가 n%할인, 이런 것들이 붙을 수도 있는데, 상품등록한 사람이 한 것일 수도 있는데, 플랫폼 쇼핑폴 차원에서 프로므션으로 붙여주는 경우가 있다. 그런 경우 원래 작성자가 사용한 상품의 이름을 바꾸지 않고, 강조 문구만 추가하고자 할 때, 해당함수 사용하면 유용함.
FUNCTION 삭제하기
DROP FUNCTION add_event_prefix
FUNCTION 호출하기
SELECT `id`, add_event_prefix(`name`) as `name`, `price` from product where id = 1;
Function은 CallableStatemnet를 이용해서 호출할 수도 있고, 다른 query statement 객체의 SQL 문에서 곧바로 사용할 수도 있다.
CallableStatement를 이용하는 방식의 위 Procedure Call 할 때 사용한 방식과 같다.
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import org.de.jdbc.mapper.ResultSetMapper;
public class Main {
public static void main(String[] args) throws SQLException {
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/de-jdbc", "root", null);
System.out.println("===== Direct Function Call =====");
CallableStatement stmtFunctionCall = con.prepareCall("{?= call add_event_prefix(?)}");
String originalContent = "original";
System.out.println("original content: " + originalContent);
stmtFunctionCall.setString(2, originalContent);
stmtFunctionCall.registerOutParameter(1, Types.VARCHAR);
boolean result2 = stmtFunctionCall.execute();
System.out.println(result2);
System.out.println("after prefix: " + stmtFunctionCall.getString(1));
System.out.println("===== Function Call in Query =====");
Statement statement = con.createStatement();
ResultSet rs = statement.executeQuery(
"SELECT `id`, add_event_prefix(`name`) as `name`, `updated_at`, `contents`, `price` from product where id = 1;");
while (rs.next()) {
ResultSetMapper.printRs(rs);
}
}
}