Sunteți pe pagina 1din 21
In this chapter, we will get basics of JDBC. JDBC helps us to connect java

In this chapter, we will get basics of JDBC. JDBC helps us to connect java application to several databases like sql server, oracle etc. and you must know 95% applications require database at backend. So we must have knowledge how to connect database to a java application. Let’s shut the mouth, and start our work.

16.1: JDBC: Short for Java Database Connectivity, a Java API that defines interfaces and classes
16.1: JDBC:
Short for Java Database Connectivity, a Java API that defines interfaces and classes
for writing database applications in Java by making database connections. Using JDBC
you can send SQL, PL/SQL statements to almost any relational database. JDBC is a
Java API for executing SQL statements and supports basic SQL functionality. It provides
RDBMS access by allowing you to embed SQL inside Java code. Since nearly all
relational database management systems (RDBMSs) support SQL, and because Java
itself runs on most platforms, JDBC makes it possible to write a single database
application that can run on different platforms and interact with different DBMSs.
JDBC was developed by JavaSoft, a subsidiary of Sun Microsystems.
Java Database Connectivity is similar to Open Database Connectivity (ODBC) which is
used for accessing and managing database, but the difference is that JDBC is designed
specifically for Java programs, whereas ODBC is not depended upon any language.
In short JDBC helps the programmers to write java applications that manage these
three programming activities:

Ø

Ø Send queries and update statements to the database.

Ø Retrieve and process the results received from the database in answer to your query.

Connect to a data source, like a database.

16.2: JDBC Architecture:

The JDBC API supports both two-tier and three-tier processing models for database access but in general JDBC Architecture consists of two layers:

1. JDBC API: This provides the application-to-JDBC Manager connection.

2. JDBC Driver API: This supports the JDBC Manager-to-Driver Connection.

Page 1

www.magix.in

The JDBC API uses a driver manager and database-specific drivers to provide transparent connectivity to heterogeneous databases. The JDBC driver manager ensures that the correct driver is used to access each data source. The driver manager is capable of supporting multiple concurrent drivers connected to multiple heterogeneous databases.

Following is the architectural diagram, which shows the location of the driver manager with respect to the JDBC drivers and the Java application:

(JDBC Architecture)
(JDBC Architecture)

(JDBC Architecture II)

Page 2

www.magix.in

16.3: JDBC Product Components:

JDBC has four Components:

1. The JDBC API.

2. The JDBC Driver Manager.

3. The JDBC Test Suite.

4. The JDBC-ODBC Bridge.

16.3.1: JDBC API:

The JDBC API provides the facility for accessing the relational database from the Java programming language. The API technology provides the industrial standard for independently connecting Java programming language and a wide range of databases. The user not only execute the SQL statements, retrieve results, and update the data but can also access it anywhere within a network because of its "Write Once, Run Anywhere" (WORA) capabilities.

Due to JDBC API technology, user can also access other tabular data sources like spreadsheets
Due to JDBC API technology, user can also access other tabular data sources like
spreadsheets or flat files even in the heterogeneous environment. JDBC application
programming interface is a part of the Java platform that have included in Java
Standard Edition (Java SE) and the Java Enterprise Edition (Java EE) in itself.
The latest version of JDBC 4.0 application programming interface is divided into two
packages
i. java.sql
ii. javax.sql.
16.3.2: JDBC Driver Manager:
This interface manages a list of database drivers. Matches connection requests from
the java application with the proper database driver using communication subprotocol.
The first driver that recognizes a certain subprotocol under JDBC will be used to
establish a database Connection.

Internally, JDBC DriverManager is a class in JDBC API. The objects of this class can connect Java applications to a JDBC driver. DriverManager is the very important part of the JDBC architecture. The main responsibility of JDBC DriverManager is to load all the drivers found in the system properly. The Driver Manager also helps to select the most appropriate driver from the previously loaded drivers when a new open database is connected.

16.3.3: JDBC Test Suite:

The function of JDBC driver test suite is to make ensure that the JDBC drivers will run user's program or not. The test suite of JDBC application program interface is very useful for testing a driver based on JDBC technology during testing period. It ensures the requirement of Java Platform Enterprise Edition (J2EE).

Page 3

www.magix.in

16.3.4: JDBC-ODBC Bridge:

The JDBC-ODBC Bridge, also known as JDBC type 1 driver is a database driver that utilizes the ODBC driver to connect the database. This driver translates JDBC method calls into ODBC function calls. The Bridge implements JDBC for any database for which an ODBC driver is available. The Bridge is always implemented as the sun.jdbc.odbc Java package and it contains a native library used to access ODBC.

Now we can conclude this topic: The first two components of JDBC, the JDBC API and the JDBC Driver Manager manages to connect to the database and then build a java program that utilizes SQL commands to communicate with any RDBMS. On the other hand, the last two components are used to communicate with ODBC or to test web application in the specialized environment.

16.4: JDBC Drivers: JDBC drivers are client-side adapters (installed on the client machine, not on
16.4: JDBC Drivers:
JDBC drivers are client-side adapters (installed on the client machine, not on the
server) that convert requests from Java programs to a protocol that the DBMS can
understand. There are commercial and free drivers available for most relational
database servers. These drivers fall into one of the following types:
16.4.1: Type-1 or JDBC/ODBC Bridge:
Type 1 drivers act as a "bridge" between JDBC and another database connectivity
mechanism such as ODBC. The JDBC- ODBC Bridge provides JDBC access using most
standard ODBC drivers. This driver is included in the Java 2 SDK within the
sun.jdbc.odbc package. In this driver the java statements are converted to JDBC
statements. JDBC statements call the ODBC by using the JDBC-ODBC Bridge. And finally
the query is executed by the database. This driver has serious limitation for many
applications.

The driver is implemented in the sun.jdbc.odbc.JdbcOdbcDriver class and comes with the Java 2 SDK, Standard Edition. The driver is platform-dependent as it makes use of ODBC which in turn depends on native libraries of the operating system. Also, using this driver has got other dependencies such as ODBC must be installed on the computer

having the driver and the database which is being connected to must support an ODBC driver. Hence the use of this driver is discouraged if the alternative of a pure-Java driver

is available.

Type 1 is the simplest of all but platform specific i.e. only to Microsoft platform.

A Type 1 driver needs to have the bridge driver installed and configured before JDBC

can be used with it. This can be a serious drawback for a production application. Type 1

drivers cannot be used in an applet since applets cannot load native code.

Page 4

www.magix.in

Functions: 1. 2. 3.
Functions:
1.
2.
3.

4.

Translates query obtained by JDBC into corresponding ODBC query, which is then handled by the ODBC driver. Sun provides a JDBC-ODBC Bridge driver. sun.jdbc.odbc.JdbcOdbcDriver. This driver is native code and not Java, and is closed source. Client -> JDBC Driver -> ODBC Driver -> Database There is some overhead associated with the translation work to go from JDBC to ODBC.

Advantages:

1. Almost any database, for which ODBC driver is installed, can be accessed.

Disadvantages:

1. Performance overhead since the calls have to go through the JDBC overhead bridge to the ODBC driver, then to the native db connectivity interface.

2. The ODBC driver needs to be installed on the client machine.

3. Considering the client-side software needed, this might not be suitable for applets.

Page 5

www.magix.in

16.4.2: Type-2 or Native-API Driver (Partly Java Driver):

Type 2 drivers use the Java Native Interface (JNI) to make calls to a local database library API. This driver converts the JDBC calls into a database specific call for databases such as SQL, ORACLE etc i.e. this driver is specific to a particular database. This driver communicates directly with the database server. It requires some native code to connect to the database. Type 2 drivers are usually faster than Type 1 drivers. Like Type 1 drivers, Type 2 drivers require native database client libraries to be installed and configured on the client machine.

The type 2 driver is not written entirely in Java as it interfaces with non-Java code that makes the final database calls. The driver is compiled for use with the particular operating system. For platform interoperability, the Type 4 driver, being a full-Java implementation, is preferred over this driver.

a full-Java implementation, is preferred over this driver. Functions: 1. This type of driver converts JDBC

Functions:

1. This type of driver converts JDBC calls into calls to the client API for that database. 2. Client -> JDBC Driver -> Vendor Client DB Library -> Database.

Advantages:

1. Better performance than Type 1 since no jdbc to odbc translation is needed.

Page 6

www.magix.in

Disadvantages:

1. Native API must be installed in the Client System and hence type 2 drivers cannot be used for the Internet.

2. Like Type 1 drivers, it’s not written in Java Language which forms a portability issue.

3. If we change the Database we have to change the native api as it is specific to a database.

4. Mostly obsolete now.

5. Not all databases give the client side library.

16.4.3: Type-3 or Network-Protocol Driver (Pure Java Driver):

The JDBC type 3 driver, also known as the network-protocol driver is a database driver
The JDBC type 3 driver, also known as the network-protocol driver is a database
driver implementation which makes use of a middle-tier between the calling program
and the database. The middle-tier (application server) converts JDBC calls directly or
indirectly into the vendor-specific database protocol.
Type 3 database requests are passed through the network to the middle-tier server. The
middle-tier then translates the request to the database. If the middle-tier server can in
turn use Type1, Type 2 or Type 4 drivers.
Type 3 JDBC drivers are the most flexible JDBC solution because they do not require
any native binary code on the client. A Type 3 driver does not need any client
installation.

(Type 3: All Java/ Net-Protocol Driver)

Page 7

www.magix.in

Functions:

1. Follows a three tier communication approach.

2. Can interface to multiple databases - Not vendor specific.

3. The JDBC Client driver written in java communicates with a middleware-net-server using a database independent protocol, and then this net server translates this request into database commands for that database.

4. Thus the client driver to middleware communication is database independent.

5. Client -> JDBC Driver -> Middleware-Net Server -> Any Database.

Advantages:

1.

Since the communication between client and the middleware server is database independent, there is no need for the vendor db library on the client machine. Also the client to middleware need'nt be changed for a new database. The Middleware Server (Can be a fully fledged J2EE Application server) can provide typical middleware services like caching (connections, query results, and so on), load balancing, logging, auditing etc Can be used in internet since there is no client side software needed. This driver is fully written in Java and hence Portable. It is suitable for the web. This driver is very flexible allows access to multiple databases using one driver. They are the most efficient amongst all driver types.

2. 3. 4. 5. 6. Disadvantages: 1. Requires database-specific coding to be done in the
2.
3.
4.
5.
6.
Disadvantages:
1. Requires database-specific coding to be done in the middle tier.
2. An extra layer added may result in a time-bottleneck. But typically this is
overcome by providing efficient middleware services.
16.4.4: Type-4 or Native Protocol Driver (Pure Java Driver):

Type 4 drivers are pure Java drivers that implement a proprietary database protocol (like Oracle's SQL*Net) to communicate directly with the database. Like Type 3 drivers, they do not require native database libraries and can be deployed over the Internet without client installation. One drawback to Type 4 drivers is that they are database specific. Unlike Type 3 drivers, if your back-end database changes, you may save to purchase and deploy a new Type 4 driver (some Type 4 drivers are available free of charge from the database manufacturer). However, because Type 4 drivers communicate directly with the database engine rather than through middleware or a native library, they are usually the fastest JDBC drivers available. This driver directly converts the java statements to SQL statements.

As the database protocol is vendor-specific, separate drivers, usually vendor-supplied, need to be used to connect to the database.

Page 8

www.magix.in

(Type 4: Native-protocol/all-Java driver) Functions: 1. Type 4 drivers are entirely written in Java that
(Type 4: Native-protocol/all-Java driver)
Functions:
1. Type 4 drivers are entirely written in Java that communicates directly with a
vendor's database through socket connections. No translation or middleware
layer, are required, improving performance.
2. The driver converts JDBC calls into the vendor-specific database protocol so that
client applications can communicate directly with the database server.
3. Completely implemented in Java to achieve platform independence.
4. Client Machine -> Native protocol JDBC Driver -> Database server.
Advantages:
1.

These drivers don't translate the requests into an intermediary format (such as ODBC), nor do they need a middleware layer to service requests. Thus the performance may be considerably improved.

2. All aspects of the application to database connection can be managed within the JVM; this can facilitate easier debugging.

Disadvantages:

1. At client side, a separate driver is needed for each database.

2. Drivers are database dependent.

Page 9

www.magix.in

16.5: Using JDBC :

There are seven steps for using JDBC:

1. Load the driver

2. Define the Connection URL

3. Establish the Connection

4. Create a Statement object

5. Execute a query

6. Process the results

7. Close the connection

16.5.1: Load the Driver :

In this step of the JDBC connection process, we load the driver class by calling
In this step of the JDBC connection process, we load the driver class by calling
Class.forName( ) with the Driver class name as an argument. Once loaded, the
Driver class creates an instance of itself. A client can connect to Database Server
through JDBC Driver. Since most of the Database servers support ODBC driver therefore
JDBC-ODBC Bridge driver is commonly used.
The return type of the Class.forName(String ClassName) method is “Class”. Class is a
class in java.lang package.
Here is an example:
try
{
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver”); //For type-1 driver.
//Class.forName("connect.microsoft.MicrosoftDriver"); for MS driver
//Class.forName("oracle.jdbc.driver.OracleDriver"); for Oracle driver
//Class.forName("com.sybase.jdbc.SybDriver"); for sybase driver
}
catch(ClassNotFoundException e)
{

System.err.println("Error loading driver: " + e);

}

The string taken in the Class.forName() method is the fully qualified class name. Anyone can check vendor's documentation to find it out. Most database vendors supply free JDBC drivers for their databases, but there are many third-party vendors of drivers for older databases.

Page 10

www.magix.in

16.5.2: Define the Connection URL :

Once we have loaded the JDBC driver, you need to specify the location of the database server. Generally, the url format is: jdbc:vendordb:userinfo plus server host, port number and database name. We should check vendor’s documentation for exact information about such format. The following is an example:

//for one of Oracle drivers String host = "dbhost.yourcompany.com"; String dbName = "someName"; int port = 1234; String oracleURL = "jdbc:oracle:thin:@" + host + ":" + port + ":" + dbName ;

//for sybase driver String host = "dbhost.yourcompany.com"; String dbName = "someName"; int port =
//for sybase driver
String host = "dbhost.yourcompany.com";
String dbName = "someName";
int port = 1234;
String sybaseURL = "jdbc:sybase:Tds:" + host + ":" + port + ":" + "?SERVICENAME=" + dbName;
16.5.3: Establish the Connection :
To make the actual network connection, pass the ‘URL’, the ‘database username’,
and the ‘password’ to the getConnection method of the DriverManager class, as
illustrated in the following example.
String username = "Suraj Arora";
String password = "secret";
Connection connection = DriverManager.getConnection(oracleURL, username, password);
16.5.4: Create a Statement :
A Statement object is used to send queries and commands to the database and is
created from the Connection as follows:

Statement statement = connection.createStatement();

16.5.5: Execute a Query :

Once you have a Statement object, you can use it to send SQL queries by using the executeQuery method, which returns an object of type ResultSet. Here is an example:

String query = "SELECT col1, col2, col3 FROM sometable"; ResultSet resultSet = statement.executeQuery(query);

*To modify the database, use executeUpdate instead of executeQuery, and supply a string that uses UPDATE, INSERT, or DELETE. *

Page 11

www.magix.in

16.5.6: Process the Result :

The simplest way to handle the results is to process them one row at a time, using the ResultSet’s next method to move through the table a row at a time. Within a row, ResultSet provides various getXxx methods that take a column index or column name as an argument and return the result as a variety of different Java types. For instance, use getInt if the value should be an integer, getString for a String, and so on for most other data types. If you just want to display the results, you can use getString regardless of the actual column type. However, if you use the version that takes a column index, note that columns are indexed starting at 1 (following the SQL convention), not at 0 as with arrays, vectors, and most other data structures in the Java programming language.

Note that the first column in a ResultSet row has index 1, not 0. Here
Note that the first column in a ResultSet row has index 1, not 0. Here is an
example that prints the values of the first three columns in all rows of a ResultSet.
while(resultSet.next())
{
System.out.println(results.getString(1) + " " + results.getString(2) + " " + results.getString(3));
}
16.5.7: Close the Connection :
To close the connection explicitly, we should do:
connection.close();
We should postpone this step if we expect to perform additional database operations,
since the overhead of opening a connection is usually large. In fact, reusing existing
connections is such an important optimization.

16.6: Example:

Let's put all things together.

Here we learn how to connect to Microsoft Access database. In order to make the following code workable, we have to set up environment first. Following the steps below:

Page 12

www.magix.in

Page 13 www.magix.in

Page 13

www.magix.in

Type in the name "judydriver" (no quotes -- any name you want to use) for the Data Source Name

Click Select Button to select a database name (or create a new one) on the driver "e:\mdbTest.mdb" for this exercise purpose.

click three OK buttons out. Run the program

The data source name you entered should be the name of dsn in the above code.

The following example shows you how to follow the above steps to load a driver, make a connection, use Statement and PreparedStatement, and insert and query data.

import java.sql.*; public class TestDBDriver { static Connection con; static Statement stmt; static ResultSet rs;
import java.sql.*;
public class TestDBDriver
{
static Connection con;
static Statement stmt;
static ResultSet rs;
public static void main(String[] args)
{
//step 1: load driver
loadDriver();
//step 3: establish connection
makeConnection();
//create a table
createTable();

//insert data

insertData();

//use precompiled statement to update data usePreparedStatement();

//retrieve data retrieveData();

//close all resources closeAll();

}

Page 14

www.magix.in

// load a driver

static void loadDriver()

{

try {

//step 2: Define connection URL Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

}

catch(java.lang.ClassNotFoundException e)

{

System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage());

}

} // make a connection step 3: establish connection static void makeConnection() { //for how
}
// make a connection step 3: establish connection
static void makeConnection()
{
//for how to set up data source name see below.
String dsn = "judydriver";
String url = "jdbc:odbc:" + dsn;
try
{
con = DriverManager.getConnection(url, "", "");
}
catch(SQLException ex)
{
System.err.println("database connection: " + ex.getMessage());
}
}

//create a table

static void createTable()

{

String createString = "create table COFFEES " + "(COF_NAME VARCHAR(32), " + "SUP_ID INTEGER, " + "PRICE FLOAT, " + "SALES INTEGER, " + "TOTAL INTEGER)";

Page 15

www.magix.in

try

{

//step 4: create a statement stmt = con.createStatement(); //step 5: execute a query or update. //stmt.execute("drop table COFFEES");//if exists, drop it, get new one stmt.executeUpdate(createString);

}

catch(SQLException ex)

{

System.err.println("CreateTable: " + ex.getMessage());

}

} //insert data to table COFFEES static void insertData() { try { stmt.executeUpdate("INSERT INTO COFFEES
}
//insert data to table COFFEES
static void insertData()
{
try
{
stmt.executeUpdate("INSERT INTO COFFEES " + "VALUES
('Colombian', 101, 7.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " + "VALUES
('French_Roast', 49, 8.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " + "VALUES ('Espresso',
35, 5.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " + "VALUES
('Colombian_Decaf', 101, 4.99, 0, 0)");

stmt.executeUpdate("INSERT INTO COFFEES " + "VALUES ('French_Roast_Decaf', 49,6.99, 0, 0)");

}

catch(SQLException ex)

{

System.err.println("InsertData: " + ex.getMessage());

}

}

Page 16

www.magix.in

//use PreparedStatement to precompile sql statement static void usePreparedStatement()

{

try

{

PreparedStatement updateSales;

String updateString = "update COFFEES " + "set SALES = ? where COF_NAME like ?";

updateSales = con.prepareStatement(updateString);

int [] salesForWeek = {175, 150, 60, 155, 90};

String [] coffees = {"Colombian", "French_Roast", "Espresso", "Colombian_Decaf", "French_Roast_Decaf"};

int len = coffees.length; for(int i = 0; i < len; i++) { updateSales.setInt(1, salesForWeek[i]);
int len = coffees.length;
for(int i = 0; i < len; i++)
{
updateSales.setInt(1, salesForWeek[i]);
updateSales.setString(2, coffees[i]);
updateSales.executeUpdate();
}
}
catch(SQLException ex)
{
System.err.println("UsePreparedStatement: " + ex.getMessage());
}
}
//retrieve data from table COFFEES
static void retrieveData()
{
try {
String gdta="SELECT COF_NAME, PRICE FROM COFFEES WHERE PRICE < 9.00";

//step 6: process the results. rs = stmt.executeQuery(gdta); while (rs.next())

{

String s = rs.getString("COF_NAME"); float n = rs.getFloat("PRICE");

System.out.println(s + "

" + n);

}

}

catch(SQLException ex)

{

System.err.println("RetrieveData: " + ex.getMessage());

}

}

Page 17

www.magix.in

//close statement and connection //step 7: close connection, etc. static void closeAll()

{

try

{

stmt.close();

con.close();

}

catch(SQLException ex)

{

System.err.println("closeAll: " + ex.getMessage());

} } } 16.7: JDBC Classes and Interfaces: The JDBC API provides mainly the following
}
}
}
16.7: JDBC Classes and Interfaces:
The JDBC API provides mainly the following interfaces and classes:
DriverManager: This interface manages a list of database drivers. Matches
connection requests from the java application with the proper database driver
using communication subprotocol. The first driver that recognizes a certain
subprotocol under JDBC will be used to establish a database Connection.
Driver: This interface handles the communications with the database server. You
will interact directly with Driver objects very rarely. Instead, you use
DriverManager objects, which manages objects of this type. It also abstracts the
details associated with working with Driver objects

Connection : Interface with all methods for contacting a database. The connection object represents communication context, i.e., all communication with database is through connection object only.

Statement : You use objects created from this interface to submit the SQL statements to the database. Some derived interfaces accept parameters in addition to executing stored procedures.

ResultSet: These objects hold data retrieved from a database after you execute an SQL query using Statement objects. It acts as an iterator to allow you to move through its data.

SQLException: This class handles any errors that occur in a database application.

Page 18

www.magix.in

16.8: JDBC Class Methods:

JDBC classes has got thousands of methods, which all can’t be discussed here. Most of the time, the JDBC interface is used to run SELECT queries and parsing through the returning result sets. If you are look at Java documentation, you will find these JDBC classes methods that you need to use most of the time:

DriverManager.getConnection() - Establish and return a connection object.

Connection.close() - Closes the connection to the database server.

Connection.getMetaData() - Returns meta data from the database server to provide information like table structures, stored procedures and connection settings.

Connection.createStatement() - Returns a statement object to execute SQL statements. Statement.executeUpdate() -
Connection.createStatement() -
Returns a statement object to execute SQL
statements.
Statement.executeUpdate() - Execute a SQL statement (non-SELECT statement)
that returns no result set.
Statement.executeQuery() - Execute a SQL SELECT statement and returns a result
set object.
Statement.close() - Closes the resources used by this statement object.
ResultSet.first() - Moves the cursor to the first row in this ResultSet object.
ResultSet.last() - Moves the cursor to the last row in this ResultSet object.
ResultSet.next() - Moves the cursor forward one row from its current position.

ResultSet.previous() - Moves the cursor to the previous row in this ResultSet object. ResultSet.getBoolean() - Returns the value as boolean of a given column. ResultSet.getByte() - Returns the value as byte of a given column. ResultSet.getDate() - Returns the value as Date of a given column. ResultSet.getDouble() - Returns the value as double of a given column. ResultSet.getFloat() - Returns the value as float of a given column. ResultSet.getInt() - Returns the value as int of a given column. ResultSet.getLong() - Returns the value as long of a given column. ResultSet.getString() - Returns the value as String of a given column. ResultSet.wasNull() - Returns true if the column fetched by the last get*() method has a null value. ResultSet.close() - Closes the resources used by this ResultSet object.

Page 19

www.magix.in

Let’s rewind some facts:

Let’s rewind some facts: Page 20 www.magix.in

Page 20

www.magix.in

Your Turn

Q.: Give Different types of JDBC Drivers? Q.: Describe basic components of JDBC? Q.: Give a short example of using JDBC? Q.: Why we use executeUpdate( ) and executeQuery() methods? Q.: How to process the results returned by JDBC in response to an SQL command?

Here we can say that the journey of java according to your syllabus ends here.
Here we can say that the journey of java according to your syllabus
ends here. But just to clarify, it’s just beginning of java that you had
learned. Picture to abhi baki hai mere dost. But again, according to
your syllabus you can take a kitkat break now.
Hope you enjoyed the journey of java till now. So, here we say Ta-ta
to you, but we will meet again for other subjects. Magix.in will
always be in your service.
Bye-Bye

Page 21

www.magix.in