- Refer 
this Spring guide for details. 
        - Hibernate User Guide can be found 
here.
        
        - Entire code can be found on GitHub 
here. 
        - On 
Initializr create a new Gradle project with the following
        dependencies: Spring Data JPA, MySQL Driver, Spring Web. 
        - Remove the 
sourceCompatibility = '11' from 
build.gradle file if giving build errors.
        
        - Spring Boot gives you defaults on all things. For example, the default database is H2. Consequently, when you
        want to use any other database, you must define the connection attributes in the
        
application.properties file. 
        - For this, we would first have to separately create a new database in MySQL. We are also creating a new table.
        Alternatively, you can just create the database and have Hibernate create the table for you. We will see how to
        do it.
        
            
                Expand Gist 
             
            
                
            
         
        - Optional, but you can add the database that you just created as a datasource to IntelliJ itself so that you
        can run SQL queries from the IDE and not have to go to WorkBench everytime. 
        
        Step 1: Select the Database tab on the right and add a datasource by clicking the plus symbol.
        
        
        Step 2: Add User, Password, Database. For your user/password combination of database, go to your MySQL
        WorkBench. Go to home, there you will see the MySQL connections. One of the existing connections is your
        user/password combination for this that you will enter in your IDE.
        Click on Test Connection. If everything was set-up correctly, you should
        see a similar message as below. Note the URL, we will be making use of it.
        
        
        Step 3: Now you can run SQL commands directly in the IDE and see the output in the window below.
        
        
        - Moving on, we now need to add the following to the 
application.properties file
        
            
                Expand Gist 
             
            
                
            
         
        Note the 
spring.jpa.hibernate.ddl-auto=none property above. 
        a) 
none: The default for MySQL. No change is made to the database structure. 
        b) 
update: Hibernate changes the database according to the given entity structures. 
        c) 
create: Creates the database every time but does not drop it on close. 
        d) 
create-drop: Creates the database and drops it when 
SessionFactory closes. 
        You must begin with either 
create or 
update, because you do not yet have the database
        structure. After the first run, you can switch it to 
update or 
none, according to
        program requirements. Use 
update when you want to make some change to the database structure.
        The default for H2 and other embedded databases is 
create-drop. For other databases, such as MySQL,
        the default is 
none. Since we have already created the table, we leave this as 
none.
        
        - Next we create the Entity model. 
        - Note how we are generating the Primary Key. It's different from the way described in the docs. 
GenerationType.SEQUENCE
        is the preferred way of generating IDs in Hibernate. But MySQL does
        not support sequences. Hence we are doing it this way.
        
This SO Link  goes into why we are using it this
        way. Also this 
        Thorben  link explains in more details why we are using the native generator instead.
        
            
                Expand Gist 
             
            
                
            
         
        - Next we create the 
StudentRepository
        
            
                Expand Gist 
             
            
                
            
         
        - Lastly we need to create a Controller to handle HTTP requests to our application.
        
            
                Expand Gist 
             
            
                
            
         
        
        
        What is REST
        
            - 
Source
            to in 28 mins from where the text is taken 
            - 
Source to Spring REST Tutorial Doc that contains
            the code for creating a REST-ful API 
            - First of all, REST does not define a standard message exchange format. You can build REST services with
            both XML and JSON. However, JSON is a more popular format with REST. So, if it does not define a standard
            message exchange format, what is REST then? REST stands for REpresentational State Transfer. Key abstraction
            in REST is a Resource. There is no restriction on what can be a resource. A todo is a resource. A person on
            facebook is a resource. 
            - A resource has an URI (Uniform Resource Identifier): 
            /user/Ranga/todos/1 
            /person/Ranga 
            - A resource will have representations: XML, HTML, JSON. 
            - A resource will have state. The representation of a resource should capture its current state. When a
            resource is requested, we provide the representation of the resource.
        
 
        
        REST and HTTP
        
            - REST builds on top of HTTP (Hypertext Transfer Protocol). HTTP is the language of the web.
            - HTTP has a few important verbs: 
            
                POST - Create a new resource 
                GET - Read a resource 
                PUT - Update an existing resource 
                DELETE - Delete a resource 
            
            - HTTP also defines some standard response codes: 
            
                - 200 - SUCESS
 
                - 404 - RESOURCE NOT FOUND
 
                - 400 - BAD REQUEST
 
                - 201 - CREATED
 
                - 401 - UNAUTHORIZED
 
                - 415 - UNSUPPORTED TYPE - Representation not supported for the resource
 
                - 500 - SERVER ERROR
 
            
         
        
        Restful Service Constraints
        
            a) Client - Server : There should be a service producer and a service consumer. 
            b) The interface (URL) is uniform and exposing resources. Interface uses nouns (not actions) 
            c) The service is stateless. Even if the service is called 10 times, the result must be the same. 
            d) The service result should be Cacheable. HTTP cache, for example. 
            e) Service should assume a Layered architecture. Client should not assume direct connection to server - it
            might be getting info from a middle layer - cache.
        
        
        Designing RESTful APIs
        
            - Following are the important things to consider when designing RESTful API's: 
            a) In Rest, we think Nouns (resources) and NOT Verbs (NOT actions). So, URI's should represent resources.
            URI's should be hierarchical and as self descriptive as possible. Prefer plurals. 
            b) Always use HTTP Methods. 
            c) GET : Should not update anything. Should be idempotent (same result in multiple calls).
            Possible Return Codes 200 (OK) + 404 (NOT FOUND) + 400 (BAD REQUEST) 
            d) POST : Should create new resource. Ideally return JSON with link to newly created resource.
            Same return codes as get possible. In addition : Return code 201 (CREATED) is possible. 
            e) PUT : Update a known resource. ex: update client details. Possible Return Codes : 200(OK)
            
            f) DELETE : Used to delete a resource. 
        
        
        What is ResponseEntity and what is it used for
        
            -
        
     
    
        - 
Oracle JDBC Reference link 
        - 
Check this Balus C link as
        well
        
        -
        
What is the JDBC API?
        
            - The JDBC API provides a standard database-independent interface to interact with any tabular data
            source. Goal is to interact with databases using a Java program where the Java program does not need to know
            what kind of database is installed/the program has to talk to. 
            - Using the JDBC API to access data in a database hides the implementation differences that exist in
            different types of databases. It achieves database transparency by defining most of its API using interfaces
            and letting the database vendors (or any third-party vendors) provide the implementations for those
            interfaces. The collection of the implementation classes that is supplied by a vendor to interact with a
            specific database is called a 
JDBC Driver 
            - Some JDBC drivers are written in pure Java. For purely Java-implemented JDBC drivers, you just need to
            include the vendor-supplied classes in your application CLASSPATH. Some JDBC drivers need a proprietary
            software installation on the client machine to interact with a database. 
            - There are different kinds of JDBC Drivers:
            

            - We are going to only use JDBC Drivers. The JDBC driver is also known as a direct-to-database pure Java
            driver. It is written in pure Java. It converts the JDBC calls into DBMS-specific calls and sends the calls
            directly to the database. It is the best suitable driver type to be used in apps. All you need to do is
            to include the driver JAR/ZIP files with your application. All major DBMS vendors supply this type
            of JDBC driver.
        
 
        Connecting to a DB
        
            - Dummy project for setting up project is present 
here on my
            GitHub. 
            - Once you have obtained the JDBC Driver from the Oracle/Maven repository, you need to register the JDBC
            Driver with your application. A JDBC Driver is a class that implements the 
java.sql.Driver
            interface. DBMS vendors supply the JDBC driver class along with any other classes it uses. 
            - You need to register this JDBC Driver with the 
java.sql.DriverManager class. (Refer pg 396 of
            book if confused). You can register multiple JDBC Drivers with the DriverManager. 
            - When you need to establish a connection to a database, you must pass a connection URL to the 
DriverManager.
            The 
DriverManager passes the
            connection URL to all registered drivers one by one, and asks them to connect to the database using
            information
            that you supply in the connection URL. If a driver recognizes the connection URL, it connects to the
            database and
            returns the connection to the 
DriverManager. An object of the 
java.sql.Connection
            interface represents a database
            connection in a Java program. If none of the registered drivers recognize a connection URL, the 
DriverManager
            will
            throw a 
SQLException stating that it could not find a suitable driver 
            - There are 3 ways to register a JDBC Driver with the 
DriverManger class: 
            a) By setting the 
jdbc.drivers system property 
            b) By loading the driver class into the JVM 
            c) By using the 
registerDriver() method of the DriverManager class 
        
 
        Constructing a Connection URL
        
            - A database connection is established using a connection URL. The format of a connection URL is dependent
            upon the DBMS and a JDBC driver. 
            - The syntax to define the Connection URL is:
            
<protocol>:<sub-protocol>:<data-source-details>, The
            
<protocol> part is always set to jdbc. The 
<sub-protocol> part is
            vendor-specific. The 
<data-source-details> part is DBMS specific that is used to locate
            the database.
            In some cases, you can also specify some connection properties in this last part of the URL. 
            - For OracleDB it is: 
jdbc:oracle:thin:hr/hr@//localhost:1521/orclpdb. The protocol part is
            jdbc. The sub-protocol part is oracle:thin, which identifies the Oracle Corporation as the vendor, and the
            type of the driver it will use, which is thin. The data source details part is @localhost:1521/orclpdb. It
            has three subparts. The @localhost identifies the server name. You could use an IP address or a machine name
            of your Oracle database server instead. Then, it contains the port number at which Oracle's Transport
            Network Substrate (TNS) listener is running. The last part is an Oracle's instance name, which is
            orclpdb in this example. Note that the above example uses a Service Name to connect to the DB instead of a
            SID. Hence the Connection URL is formatted differently. Refer
            
this
                SO Link 
            to read what is different and how it is formatted.
        
 
        Establishing the DB Connection
        
            - You need to use the 
getConnection() static method of the 
DriverManager class
            to establish a connection to a database. It returns an object of the 
java.sql.Connection
            interface, which represents the database connection. The 
getConnection() method takes a
            connection URL, a user id, a password, and any number of name-value pairs using a
            
java.util.Properties object. The 
getConnection() method is overloaded. 
            - The Connection interface inherits from the 
java.lang.AutoCloseable interface. That means you
            can also use a try-with-resources block to obtain a 
Connection that will be automatically
            closed when the control exits the try block. 
            - When you are done with a database connection, you need to close it by using the 
close()
            method of the 
Connection object.
            
            - View the entire code 
here.
            
                
                    Expand Gist 
                 
                
                    
                
             
         
        Setting the Auto-Commit Mode
        
            - When you connect to a database, the auto-commit property for the Connection() object is set
            to true by default. If a connection is in an auto-commit mode, a SQL statement is committed automatically
            after its successful execution. If a connection is not in an auto-commit mode, you must call the commit()
            or rollback() method of the Connection() object to commit or rollback a
            transaction. Typically, you disable the auto-commit mode for a connection in a JDBC application, so your
            application logic controls the final outcome of the transaction. To disable the auto-commit mode,
            you need to call the setAutoCommit(false) on the Connection() object after
            connection has been established (like we have done above). 
            - If you have enabled the auto-commit mode for your connection, you cannot use its commit() and
            rollback() methods. Calling the commit() and rollback() methods on a
            Connection object, which has enabled the auto-commit mode, throws an exception. JDBC also lets
            you use save points in a transaction, so that you can apply a partial rollback to a transaction. 
        
        Transaction Isolation Level
        
            - In a multi-user database, you will often come across the following two terms: 
            a) Data concurrency: Data concurrency refers to the ability of multiple users to use the same data
            concurrently. 
            b) Data consistency: Data consistency refers to the accuracy of the data that is maintained when multiple
            users are manipulating the data concurrently. 
            - As the data concurrency increases (i.e. more users work on the same data), care must be taken to maintain
            a desired level of data consistency. A database maintains data consistency using locks and by isolating one
            transaction from another. How much a transaction is isolated from another transaction depends on the desired
            level of data consistency. 
            - There are three phenomena where data consistency may be compromised in a multi-user environment where
            multiple concurrent transactions are supported: 
            a) 
Dirty Read: In a dirty read, a transaction reads uncommitted data from another transaction.
            Consider the
            following sequence of steps, which results in inconsistent data because of a dirty read:
            
Transaction A inserts a new row in a table and it has not committed it yet.
            Transaction B reads the uncommitted row inserted by the transaction A.
            Transaction A rollbacks the changes.
            At this point, transaction B is left with data for a row that does not exist.
            
            b) 
Non-Repeatable Read: In a non-repeatable read, when a transaction re-reads the data, it finds that
            the
            data has been modified by another transaction that has been already committed. Consider the following
            sequence of steps, which results in inconsistent data because of a non-repeatable read:
            
Transaction A reads a row.
            Transaction B modifies or deletes the same row and commits the changes.
            Transaction A re-reads the same row and finds that the row has been modified or deleted.
            
            c) 
Phantom Read: In a phantom read, when a transaction re-executes the same query, it finds more data
            that
            satisfies the query. Consider the following sequence of steps, which results in inconsistent data, because
            of a phantom read:
            
Transaction A executes a query (say Q) and finds X number of rows matching the query
            Transaction B inserts some rows that satisfy the query Q criteria and commits.
            Transaction A re-executes the same query (Q) and finds Y number of rows (Y > X) matching the query.
            
            - 
SO
            Link  explaining the difference between Non-Repeatable Read and Phantom Read. (Basically: A
            non-repeatable read occurs, when during the course of a transaction, a row is retrieved twice and the values
            within the row differ between reads. A phantom read occurs when, in the course of a transaction, two
            identical queries are executed, and the collection of rows returned by the second query is different from
            the first. So, while the Non-Repeatable Read applies to a single row, the Phantom Read is about a range of
            records which satisfy a given query filtering criteria.) 
            - The ANSI SQL-92 standard defines four transaction isolation levels in terms of the above-described three
            situations for data consistency. Each isolation level defines what kinds of data inconsistencies are
            allowed, or not allowed. The four transaction isolation levels and their correspondiYou can check the
            updated value by runningng constants in Java are
            as follows:
            
Read Uncommitted (TRANSACTION_READ_UNCOMMITTED)
            Read Committed (TRANSACTION_READ_COMMITTED)
            Repeatable Read (TRANSACTION_REPEATABLE_READ)
            Serializable (TRANSACTION_SERIALIZABLE)
            
            - The below table shows the four isolation levels and the three permitted situations. It is up to a DBMS to
            decide how they implement these isolation levels.
            
                
                
                    | Isolation Level | 
                    Dirty Read | 
                    Non-Repeatable Read | 
                    Phantom Read | 
                
                
                
                
                    | Read Uncommitted | 
                    Permitted | 
                    Permitted | 
                    Permitted | 
                
                
                    | Read Committed | 
                    Not Permitted | 
                    Permitted | 
                    Permitted | 
                
                
                    | Repeatable Read | 
                    Not Permitted | 
                    Not Permitted | 
                    Permitted | 
                
                
                    | Serializable | 
                    Not Permitted | 
                    Not Permitted | 
                    Not Permitted | 
                
                
            
            - Even if the SQL standard mandates the use of the 
TRANSACTION_SERIALIZABLE isolation level,
            most database management systems use a different default level. Oracle, for example, has a default isolation
            level of 
TRANSACTION_READ_COMMITTED (Source). 
            - 
This link from
            Vlad's blog explains the isolation levels. 
This
            SO Link explains the difference between 
TRANSACTION_READ_COMMITTED and 
TRANSACTION_REPEATABLE_READ.
        
 
        JDBC-Types-to-Java-Types Mapping
        
            - The JDBC API allows you to access and manipulate data stored in a database in a Java environment. The
            database uses its own data types, whereas Java uses its own. We use three sets of methods while
            working with data in JDBC programs: getXxx(), setXxx(), and updateXxx(), where Xxx indicates a data type
            such as int, String, Date, etc. These methods are found in many interfaces such as PreparedStatement,
            ResultSet. 
            - A getXxx() method is used to read data from a JDBC environment to a Java program. A setXxx() method is
            used to set a value in a Java program that will finally be passed to a JDBC environment. An updateXxx()
            method is used to update a data element that was retrieved from a JDBC environment and the updated value
            will be passed again to a JDBC environment. 
        
        Executing SQL Statements
        
            - Based on the type of work that a SQL statement performs in a DBMS, it can be categorized as follows: 
            a) Data Definition Language (DDL) Statement: Examples are CREATE TABLE,
            ALTER TABLE. 
            b) Data Manipulation Language (DML) Statement: SELECT, INSERT, UPDATE,
            DELETE 
            c) Data Control Language (DCL) Statement: GRANT, REVOKE 
            d) Transaction Control Language (TCL) Statement: COMMIT, ROLLBACK,
            SAVEPOINT 
            
            - An instance of the STATEMENT interface represents a SQL Statement in a Java program. Java
            uses three different interfaces to represent SQL statements in different formats: 
            a) Statement: If you have a SQL statement in the form of a string, you can use a
            Statement object to execute it. The SQL statement may or may not return a result set.
            Typically, a SELECT statement returns a result set with zero or more records. The SQL
            statements in the string format are compiled each time they are executed. 
            b) PreparedStatement: You can use a PreparedStatement, if you want to
            precompile a
            SQL statement once and execute it multiple times. It lets you specify a SQL statement in the form of a
            string that uses placeholders. You need to supply the values of the placeholders before you execute the
            statement. Using a PreparedStatement object is preferred over using a Statement
            object. Refer page 414 of book for the reasons why.
            c) CallableStatement: You can use a CallableStatement object to execute a
            database-stored procedure or function in a database. The stored procedure may return result sets. 
            - The PreparedStatement inherits from the Statement interface and the CallableStatement
            inherits from the PreparedStatement
        
        Results of executing an SQL Statement
        
            - When you execute a SQL statement, the DBMS may return zero or more results. The results may include update
            counts (number of records affected in the database) or result sets (a group of records). 
            - When you execute a SELECT statement, it returns a result set. When you execute an UPDATE or DELETE
            statement, it returns an update count, which is the number of records affected in the database by the SQL.
            When you execute a stored procedure, it may return multiple update counts as well as multiple result sets. A
            JDBC driver will let you get to the results in the order they were returned from the database.
            
        
        Using the Statement interface
        
            - You can use a Statement to execute any kind of SQL statement. You use one of its three
            methods called execute(), executeUpdate(), and executeQuery() to
            execute a SQL statement. 
            - The execute() method in the Statement interface is a general-purpose method that you
            can use
            to execute any types of SQL statements. Typically, it is used to execute a SQL statement that does not
            return a result set, such as a DDL statement like CREATE TABLE. The returned value from the
            execute() method indicates the status of the returned result set. If the first result is a
            ResultSet object, it returns true. It returns false if the first result is an update count or
            no result is returned from the DBMS. 
            - The executeUpdate() method is used to execute a SQL statement that updates the data in
            the
            database such as INSERT, UPDATE and DELETE statements. It returns the
            number of rows affected in the database by the execution of the statement. You may use this method to
            execute other kinds of SQL statements, such as a CREATE TABLE statement which do not return
            anything. The method returns zero when the SQL statement does not return anything.
            You should NOT use this method to execute a SELECT statement. Java 8 has added a executeLargeUpdate()
            method that works the same as the executeUpdate() method, except that it returns a long instead
            of an int. Use this method when you expect the update count to exceed Integer.MAX_VALUE.
            - The executeQuery() method is especially designed to execute a SQL statement that
            produces one
            and only one result set. It is best suited for executing a SELECT statement. Although you can
            execute a stored procedure (which produces a result set) using this method of the Statement
            interface, you should instead use the specially designed CallableStatement interface's
            execute() method to execute a stored procedure.
        
        Steps to execute a SQL Statement using a Statement object
        
            - 
Github
            repo for the code using 
Statement interface. 
            1) Get a 
Connection object: 
            
Connection conn = JDBCUtil.getConnection(); 
            2) Create a 
Statement object using the 
createStatement() method of the 
Connection
            object. 
            
Statement stmt = conn.createStatement(); 
            3) Execute one or more SQL statements by calling one of the three methods of the 
Statement
            object. 
            
// Increase everyone's income by 10% 
            String sql = "update person set income = income * 1.1"; 
            int rowsUpdated = stmt.executeUpdate(sql); 
            // Execute other SQL statements using stmt 
            4) Close the 
Statement object to release the resources. 
            
stmt.close(); 
            5) Commit the transaction. 
            
conn.commit() 
         
        Using the PreparedStatement Interface
        
            - The PreparedStatement interface inherits from the Statement interface. It is
            preferred over the Statement interface to execute a SQL statement. It precompiles the SQL
            statement. It reuses the precompiled SQL statement if the statement is executed multiple times. It lets you
            prepare a SQL statement, which is in a string format, using placeholders for input parameters. 
            - A question mark in a SQL string is a placeholder for an input parameter whose value will be supplied
            before the statement is executed. Each placeholder has an index. The first placeholder in a SQL string is
            given an index of 1, the second placeholder an index of 2, and so on. Note that the index of the placeholder
            starts at 1, not 0. 
        
        Steps to execute a SQL Statement using a PreparedStatement object
        
            - 
Github
            repo for the code using 
PreparedStatement interface. 
            0) Create a SQL statement: 
            
String sqlQuery = "INSERT INTO person " + 
             "(person_id, first_name, last_name, gender, dob, income) " + 
             "VALUES " + 
             "(?, ?, ?, ?, ?, ?)"; 
            1) Get a 
Connection object: 
            
Connection conn = JDBCUtil.getConnection(); 
            2) Obtain a PreparedStatement for the sql. 
            
PreparedStatement pStmnt = conn.prepareStatement(sqlQuery); 
            3) Set the values for the placeholders 
            
pStmnt.setInt(1, 3); 
            pStmnt.setString(2, "Charlie"); 
            pStmnt.setString(3, "Doe"); 
            pStmnt.setString(4, "M"); 
            java.sql.Date dob = java.sql.Date.valueOf("1990-01-03"); 
            pStmnt.setDate(5, dob); 
            pStmnt.setDouble(6, 50000); 
            4) Now it is time to send the SQL statement with the values for the placeholders to the database. You
            execute a SQL statement in a 
PreparedStatement using one of its 
execute(), 
executeUpdate(),
            and 
executeQuery() methods. These methods take no arguments. Recall that the
            
Statement interface has the same methods, which take SQL strings as their arguments. The 
PreparedStatement
            interface has added three methods with the same name, which take no arguments, because it gets its SQL
            string when it is created. 
            //Execute the INSERT statement in pStmnt 
            
pstmt.executeUpdate(); 
            5) Close the 
PreparedStatement object to release the resources. 
            
pstmt.close(); 
            6) Commit the transaction. 
            
conn.commit() 
         
        How do you reuse a PreparedStatement?
        
            - Repopulate the placeholder values and call one of its execute() methods again. When you
            invoke the setXxx() method on a PreparedStatement object again, its previously set value
            for the specified placeholder is overwritten with the new value. A PreparedStatement keeps
            holding the set values for its placeholder even after it is executed. Therefore, if you want to set the same
            value for a placeholder for multiple executions, you need to set the value for that placeholder only once.
            
            - If you want to clear the values of all placeholders, you can use the clearParameters() method
            of the PreparedStatement interface.
        
        CallableStatement interface
        
            - TODO
        
        What is a ResultSet
        
            - When you execute a query (a SELECT statement) in a database, it returns the matching records
            in the form of a result set. You can consider a result set as a data arranged in rows and columns. The
            returned data obviously depends upon the SELECT statement that you send to the DB. 
            - The Statement (or PreparedStatement or CallableStatement) object
            returns the result of a query as a ResultSet object. The "Result Set" is an instance of a class
            that implements the ResultSet interface that lets you access and manipulate the returned data.
            A ResultSet object also contains information about the properties of the columns in the result
            set such as the data types of the columns, names of the columns, etc. 
            - A ResultSet object maintains a cursor, which points to a row in the result set. The
            cursor can point to only one row at a time. The row to which it points at a particular point in time is
            called the current row.
        
        Properties of a ResultSet
        
            Scrollability: Scrollability determines the ability of the ResultSet to scroll through the rows.
                By default, a ResultSet is scrollable only in the forward direction. When you have a
                forward-only scrollable ResultSet, you can move the cursor starting from the first row to
                the last row. Once you move to the last row, you cannot reuse the ResultSet object
                because you cannot scroll back in a forward-only scrollable ResultSet. 
                - You can also create
                a ResultSet that can scroll in the forward as well as the backward direction.
                This ResultSet is called a bidirectional scrollable ResultSet.
                A bidirectional scrollable ResultSet has another property called update sensitivity.
                It determines whether the changes in the underlying database will be reflected in the result set while
                you are scrolling through its rows. A scroll sensitive ResultSet shows you changes made in
                the database, whereas a scroll insensitive one would not show you the changes made in the database after
                you have opened the ResultSet. 
                - The following three constants in the ResultSet interface are used to specify the scrollability of a
                ResultSet: 
                a) TYPE_FORWARD_ONLY: Allows a ResultSet object to move only in the forward
                direction. 
                b) TYPE_SCROLL_SENSITIVE: Allows a ResultSet object to move in the forward and
                backward directions. It makes the changes in the underlying database made by other transactions or
                statements in the same transaction visible to the ResultSet object. This type of ResultSet
                is aware of the changes made to its data by other means. 
                c) TYPE_SCROLL_INSENSITIVE: Allows a ResultSet object to move in the forward
                and backward directions. It DOES NOT make the changes in the underlying database made by other
                transactions or statements in the same transaction visible to the ResultSet object while
                scrolling. This type of ResultSet determines its data set when it is open and the data set
                does not change if it is updated through any other means EXCEPT through this ResultSet
                object itself. If you want to get up-to-date data, you must re-execute the query.
            
            
            Concurrency: Concurrency refers to its ability of the ResultSet to update data. By
                default, a ResultSet is read-only and it does not let you update its data. If you want to
                update data in a database through a ResultSet, you need to request an updatable result set
                from the JDBC driver. 
                - The following two constants in the ResultSet interface are used to specify the concurrency of a
                ResultSet: 
                a) CONCUR_READ_ONLY: Makes a result set read-only. 
                b) CONCUR_UPDATABLE: Makes a result set updatable.
            
            
            Holdability: Holdability refers to the state of the ResultSet after a transaction
                that it is associated with has been committed.
                A ResultSet may be closed or kept open when the transaction is committed. The default value
                of the holdability of a ResultSet is dependent on the JDBC driver. 
                - The holdability of a ResultSet is specified using one of the following two constants
                defined in the ResultSet interface: 
                a) HOLD_CURSORS_OVER_COMMIT: Keeps the ResultSet open after the transaction is
                committed. 
                b) CLOSE_CURSORS_AT_COMMIT: Closes the ResultSet after the transaction is
                committed.
            
            
            - For an OracleDB, you can check these values by running 
this
            code. The own****AreVisible shows whether inserts/updates/deletes using the same ResultSet are visible
            within that ResultSet itself. Read more at 
this SO
            Link.
            
                
                    Expand Gist 
                 
                
                    
                
             
         
        Getting a ResultSet
        
            - Example code on Github 
here.
            
            - You can get a result set from a database using a 
Statement, a 
PreparedStatement,
            or a 
CallableStatement. In simple cases, you call 
executeQuery() method of a
            
Statement object or a 
PreparedStatement object with a 
SELECT
            statement that will return a 
ResultSet. 
            - In the beginning, the cursor points before the first row in the result set. This row is an imaginary row.
            You must move the cursor to a valid row before you can access the column's values for that row. Calling the
            
next() method moves the cursor to the next row. Hence, the first time that you get a 
ResultSet
            objet, you have to call it's 
next() method in order to read the first row of the result set.
            
            - Also, important to note that the 
next() method returns a 
boolean value. It
            returns true if the cursor is positioned to a valid row. Otherwise, it returns false. If you call the
            
next() method on an empty 
ResultSet object for the first time, it will return
            false, because there is no valid row to move to. If the current row is the last row in the result set,
            calling the 
next() method will position the cursor after the last row and it will return false.
            
            - When a cursor is positioned after the last row in a forward-only scrollable 
ResultSet object,
            you cannot do anything with it, except close it using its 
close() method. You cannot reopen a
            
ResultSet either. To iterate through the result set data again, you must re-execute the query
            and obtain a new 
ResultSet. A bidirectional scrollable 
ResultSet on the other hand
            allows you to iterate through the rows as many times as you want. 
            - When you do not create the 
ResultSet, you must know the cursor position correctly in order to
            process the rows in a specific order. The following four methods of the 
ResultSet interface let
            you know if the cursor is before the first row, on the first row, on the last row, or after the last row:
            
            
isBeforeFirst(), 
isFirst(), 
isLast(), 
isAfterLast() 
         
        Reading values from a ResultSet
        
            - A ResultSet object lets you read the value of a column from its current row using one of its
            getXxx() method, where Xxx is the data type of the column. 
            - You must specify the index or name of the column in the getXxx() method whose value you want to read. The
            getXxx() methods are overloaded. One version accepts an int parameter, which lets you use the column index
            and another version accepts a String parameter, which lets you use the column label. If the column label is
            not specified in the query, you can specify the column name. 
            - Example: SELECT person_id AS "Person ID", first_name, last_name FROM person 
            - In the ResultSet, the person_id column has a column index of 1, the first_name
            column has a column index of 2, and the last_name column has a column index of 3. You have
            specified Person ID as the column label for the person_id column. You have not
            specified the column labels for the first_name and last_name columns. To get the
            value of the person_id column, you need to use either getInt(1) or
            getInt("PERSON ID"). To get the value of the first_name column, you need to use
            either getString(2) or getString("first_name").
        
        Handling null values in a ResultSet
        
            - Example code present 
here.
            
            - In a 
ResultSet, when a column has a null value, the getXxx() method returns the default value
            for the Xxx data type. For example, for numeric data types (int, double, byte, etc.), the getXxx() method
            returns zero when the column has a null value. A getXxx() method returns false for the boolean data type
            when the column has null value. The getXxx() returns 
null if Xxx is a reference type. 
            - If you want to know whether the column value, which you read using a getXxx() method, is null, you need to
            call the 
wasNull() method immediately after calling the getXxx() method. If the
            
wasNull() method returns true, the column value is null in the result set. If the 
wasNull()
            method returns false, the column value is not null in the result set. Note that the 
wasNull()
            method does not accept any parameter and it returns null value status of the last read column using a
            getXxx() method.
        
 
        Getting the number of Rows in a ResultSet
        
            - There is no method in the ResultSet interface that returns the number of rows in the result
            set. The ResultSet interface contains a getRow() method that returns the current
            row number in the ResultSet. It returns zero if there is no current row, such as when the
            cursor is before the first row or after the last row. 
            - If you want to know the number of rows in a forward-only scrollable ResultSet BEFORE you
            start iterating over the ResultSet, the only way to do this is to get the
            ResultSet twice. But, between the time when you get the first result set and when you get the
            second result set, the data in the database might change, which will make the row
            count from the first execution invalid. 
            - Another way is to execute a separate SQL query - SELECT COUNT(*) FROM person WHERE ... once
            you are done running your SELECT statement. But you again run into the same issue of another
            transaction possibly changing the DB between the first and the second query. 
            - The solution is to use a Bidirectional Scrollable ResultSet.
        
        Using a Bidirectional Scrollable ResultSet
        
            - Code present 
here
            on Github. 
            - After you get the 
ResultSet, call its 
last() method to move its cursor to the
            last row in the result set. Call the 
getRow() method when the cursor is at the last row.
            The 
getRow() method will return the row number of the last row, which will be the number of
            rows in the result set. 
            - If you want to process the result set after getting the number of rows, you can call its 
beforeFirst()
            method to scroll the cursor before the first row and start a while-loop to process the rows in the result
            set again. 
            - After getting a 
ResultSet object, it is very important to check if it
            supports bidirectional scrolling before you call the 
last() method on it. A forward-only 
ResultSet
            object will throw a 
SQLException if you call the 
last() method. You can get the
            scrollable property of a 
ResultSet object by calling its 
getType() method.
        
 
        Bidirectional Scrollable ResultSet
        
            - 
Code
            on how to create 
Statement with Bi-directional result set. 
            - There are overloaded methods of the 
createStatement, 
prepareStatement, and
            
prepareCall that let you specify the scrollability, concurrency, and holdability of the
            
ResultSet. 
            - The default 
createStatement() and 
prepareStatement() (
link to Java
            Docs for the method )that do not accept any arguments set up the following kind of
            
ResultSet: Result sets created using the returned 
Statement object will by default
            be type 
TYPE_FORWARD_ONLY and have a concurrency level of 
CONCUR_READ_ONLY. The
            holdability of the created result sets can be determined by calling 
getHoldability(). 
            - The JDBC driver determines the default value for the holdability of a ResultSet. Different JDBC drivers
            have different default values for this property.
        
 
        ResultSet and Fetch Size
        
            - Note that a 
ResultSet does not retrieve all rows for a query at once. The number of rows a
            ResultSet will retrieve from the database is JDBC driver-dependent. 
            - You can give a hint to the JDBC driver using the 
setFetchSize(int fetchSize) method of the
            
ResultSet object to fetch a specified number of rows from the database whenever more rows are
            needed. Read the Java Docs for the 
setFetchSize here.
            Read about how the 
setFetchSize method works on SO 
here
            and
            
here.
            The difference between the 
setFetchSize() method on 
ResultSet and
            
Statement can be read on 
this
            SO Link (The only place the fetch size really matters is on the ResultSet. When a ResultSet is created,
            it gets its fetch size from the Statement that was used to create it, but it can be changed later.
            Think of the Statement's fetch size as a default that will be passed to all the ResultSets it creates. If
            you don't want to use this default, you can override it.).
            
            - Oracle specific documentation can be read 
here. This says:
            When the statement object run a query, the fetch size of the statement object is passed to the result set
            object produced by the query. However, you can also set the fetch size in the result set object to override
            the statement fetch size that was passed to it. Note: Changes made to the fetch size of a statement object
            after a result set is produced will have no affect on that result set. By default, when Oracle JDBC runs a
            query, it retrieves a result set of 10 rows at a time from the database cursor. This is the default Oracle
            row fetch size value. You can change the number of rows retrieved with each trip to the database cursor by
            changing the row fetch size value.
            - Note that there is a 
setFetchSize method on all 
Statement,
            
PreparedStatement, 
CallableStatement, and 
ResultSet objects. 
            - When does a 
ResultSet need to fetch more rows from the database?
            A 
ResultSet needs to fetch more rows if you position its cursor to a row that is not in its
            cache. For example, calling the 
next() method of a 
ResultSet may trigger a fetch
            from the database. Suppose a 
ResultSet fetches 10 records at a time. If you call the 
next()
            method the first time, it will fetch and cache 10 records and, for nine subsequent calls to its
            
next() method, it will give you rows from its cache. Fetching and caching rows for a 
ResultSet
            is dependent on a JDBC driver and the underlying DBMS. 
            - There is also a 
setMaxRows method on the 
RowSet interface as shown 
here. This is
            different from the 
setFetchSize as explained on 
this SO Link. 
            - Question: So loading the entire ResultSet into the memory is to be avoided. So suppose if we set the
            setFetchSize to 25, and then fetch the next 25 rows, ie. 26-50, are the rows from 1-25 removed from the
            memory? If I am using a bi-directional ResultSet, and decide to go back to row 1 for example, will this
            cause another trip to the OracleDB?
        
 
        Scrolling Through Rows of a ResultSet
        
            - There are two sets of rows that a cursor may point to. One set of rows consists of two imaginary rows -
            one before the first row and one after the last row. Another set of rows consists of the rows that match the
            query. 
            - You can use the 
getRow() method of the 
ResultSet interface to get the row number
            of the row at which the cursor is currently positioned. If the cursor is positioned before the first row or
            after the last row, the 
getRow() method returns zero.
            

            - If you have a 
ResultSet object that has its scrollability set to forward-only, you can only
            use its 
next() method to move the cursor, which moves its cursor one row in the forward
            direction. 
            - If a 
ResultSet has a bidirectional scrollability, you have many methods to change its cursor
            position. 
            - A cursor movement can either be an absolute movement or a relative movement. 
            - Refer page 455 of book for description about all the methods that can be used to scroll through the 
ResultSet
            
            - Use the 
last() method with caution. This method call will force the JDBC driver to retrieve
            all rows from the database. If a DBMS does not support a bidirectional scrollable cursor, a JDBC
            driver will have to cache all rows on the client. For a very large result set, it may affect the performance
            of the application adversely.
        
 
        Closing a ResultSet
        
            - You can close a ResultSet object by calling its close() method. 
            - A ResultSet object can also be closed implicitly in the following situations: 
            a) When the Statement object that produces the ResultSet object is closed, it automatically
            closes the ResultSet object. 
            b) When a Statement object is re-executed, its previously opened ResultSet object is closed. 
            c) If a Statement object produces multiple result sets, retrieving the next result set closes the
            previously retrieved ResultSet. 
            d) If it is a forward-only scrollable ResultSet, a JDBC driver may choose to close it when its
            next() method returns false as the part of optimization. Once the next() method returns
            false for a forward-only scrollable ResultSet, you cannot do anything with that ResultSet
            anyway.
        
        Making changes to a ResultSet
        
            - You can use a ResultSet to perform insert, update, and delete operations on database tables.
            The concurrency for the ResultSet object must be ResultSet.CONCUR_UPDATABLE in
            order to perform updates on the ResultSet.
        
        Inserting a Row using a ResultSet
        
            - Code to insert a row 
here
            on github. 
            - In addition to the two imaginary rows that we saw earlier, 
ResultSet has a third imaginary
            row that is called the 
insert row. You can think of this row as an empty new row, which acts as a
            staging area for a new row that you want to insert.
            - You can position the cursor to the insert row using the 
ResultSet object's 
moveToInsertRow()
            method. When the cursor moves to the insert row, it remembers its previous position. You can call the 
moveToCurrentRow()
            method to move the cursor from the insert row back to the previously current row. 
            - So the steps that you follow in order to insert a new row are as follows: 
            a) Move the cursor to an insert row to add a new row: 
            
resultSet.moveToInsertRow(); 
            b) Set the values for all the columns (at least for non-nullable columns) using one of the updateXxx()
            methods of the ResultSet interface. 
            c) Send changes to the database: 
            
resultSet.insertRow() 
            The call to the 
insertRow() method may or may not make the inserted row a permanent row in the
            database. If the auto-commit mode is enabled for the 
Connection, the 
insertRow()
            call will also commit your transaction. In that case, the new row becomes part of the database permanently.
            If the auto-commit mode is disabled for the 
Connection, you can make the insert permanent by
            committing the transaction, or cancel the insert by rolling back the transaction. 
            d) After inserting your row, you can move back to your previous current row: 
            
resultSet.moveToCurrentRow(); 
            Note that moving to another row before calling the 
insertRow() method after calling the
            
moveToInsertRow() method discards the new row. 
            
            - Question: why can't I see the values that I insert in the same ResultSet even though my ResultSet is set
            to 
TYPE_SCROLL_SENSITIVE? This 
SO Link
            explains why (You might also want to check 
DatabaseMetaData.ownInsertsAreVisible(int) and
            related methods for your specific database and driver.) You can check these values for the OracleDB by
            running
            
this
                code here. 
            Question: Why does using 
SELECT * FROM people; generate a read only ResultSet. Read at 
this SO Link. (To produce a scroll-sensitive
            result set a query cannot use "SELECT * ".) 
        
 
        Updating a Row using a ResultSet
        
            - Code to update the row 
here.
            Question asked on SO regarding behavior 
here.
            
            - Steps involved in updating an existing row in a ResultSet object: 
            1) Move the cursor to a valid row in the result set. Note that you can update data only for an
            existing row. It is obvious that the cursor should not be positioned before the first row or
            after the last row if you want to update the data in a row. 
            2) Call an updateXxx() method for a column to update the column's value. 
            3) If you do not want to go ahead with the changes made using updateXxx() method calls,
            you need to call the 
cancelRowUpdates() method of the 
ResultSet to cancel the
            changes. 
            4) When you are done updating all the column's values for the current row, call the
            
updateRow() method to send the changes to the database. If the auto-commit mode is
            enabled for the 
Connection, changes will be committed. Otherwise, you need to commit
            the changes to the database. 
            5) If you move the cursor to a different row before calling the 
updateRow(), all your changes
            made using the updateXxx() method calls will be discarded. 
            6) There is another way to lose your updates to columns in a row. If you call the
            
refreshRow() method after calling updateXxx(), but before calling 
updateRow(),
            your changes will be lost because the JDBC driver will refresh the row's data from the database.
            
            - Note that you do not have to set the scrollability of the 
ResultSet to 
TYPE_SCROLL_SENSITIVE
            in order to see your changes in the DB. Because as is specified in it's definition, "
TYPE_SCROLL_SENSITIVE
            makes the changes in the underlying database made by OTHER transactions or statements in the same
            transaction visible to the ResultSet object." So the changes made by your transaction would still be visible
            if you just left it to 
TYPE_FORWARD_ONLY. 
            - What does it mean for changes to be "visible"?
            
Source
         
        Deleting a Row using a ResultSet
        
            - Code 
here.
            
            - Unlike UPDATE and INSERT operations in a result set, which require a separate step to propagate the
            changes to the database, a DELETE operation in a result set is immediately run in the corresponding row in
            the database as well. Once you call deleteRow method, the changes are made permanent with the next
            transaction COMMIT operation.
            Remember also that by default, the auto-commit flag is set to true. Therefore, unless you override this
            default, any deleteRow method operation is run and committed immediately.
            
Oracle JDBC Docs
            Source. 
        
 
        Using RowSets
        
            - An instance of the RowSet interface is a wrapper for a result set. The RowSet
            interface inherits from the ResultSet interface.
        
        Advantages of using RowSets
        
            - When you use a ResultSet object, you must deal with the Connection and Statement
            objects at the same time. A RowSet hides the complexities of using the Connection
            and
            Statement objects from the developers. 
            - A ResultSet is not Serializable and therefore, it cannot be sent over the
            network or saved to the disk for later use. A RowSet is Serializable. It can be
            sent over the network or saved to a disk for later use. 
            - A ResultSet is always connected to a data source. A RowSet object does not need
            to be connected to its data source all the time. It can connect to the database when needed such as
            to retrieve/update data in the data source. 
            - A RowSet is by default scrollable and updatable. 
            - The two properties of a RowSet, serialization and connectionlessness, makes it very useful in
            a thin client environment such as a mobile device or a web application. A thin client does not
            need to have a JDBC driver. 
            - A RowSet also supports filtering of data after the data has been retrieved. Filtering of data
            is not possible in a ResultSet. You must use a WHERE clause in a query to filter
            data in the database itself if you use a ResultSet. 
            - A RowSet makes it possible to join two or more data sets based on their column’s values
            after they have been retrieved from their data sources.
        
        Disadvantages of using RowSets
        
            - A specific RowSet implementation may cache data in memory. You need to be careful when
            using such type of RowSets. You should not fetch large volumes of data using these RowSets.
            Otherwise, it may slow down the application. 
            - With cached data in a RowSet, there are more possibilities of data inconsistency between the
            data in the RowSet and data in the data source, when changes are applied to the data source.
        
        Creating a RowSet
        
            - Code to set up a Util Class for creating 
RowSet here.
            
            - The following interfaces in the 
javax.sql.rowset package define five types of rowsets: 
            a) JdbcRowSet 
            b) CachedRowSet 
            c) WebRowSet 
            d) FilteredRowSet 
            e) JoinRowSet 
            - Each type of rowset has features that are suitable for specific needs. All these rowset interfaces
            inherit, directly or indirectly, from the 
RowSet interface. The 
RowSet interface
            is inherited from the 
ResultSet interface. Therefore,
            all methods in the 
ResultSet interface are also available in all types of rowsets.
            
         
        JDBCRowSet
        
            - Refer how to create JDBCRowSet code to read from a table and update a table
here.
            
            - A 
JdbcRowSet is also called a 
connected rowset because it always maintains a database
            connection. 
            - You can think of a 
JdbcRowSet as a thin wrapper for a 
ResultSet. As a 
ResultSet
            always maintains a database connection, so does a 
JdbcRowSet. It adds some methods that let you
            configure the connection behaviors. 
            - Updating data using a 
JdbcRowSet is similar to updating data using a 
ResultSet.
            Make sure that you set the auto-commit mode for the rowset appropriately. 
            - Note that you must call the 
updateRow() method of the 
JdbcRowSet after updating
            the column's value and before you scroll to another row. Otherwise, your changes will be lost as it is lost
            in the case of updating data in a 
ResultSet. In case of 
JdbcRowSet, you do not
            have a direct access to the 
Connection object. You need to use 
JdbcRowSet object's
            
commit() and 
rollback() methods to commit and rollback changes to the database.
        
 
        CachedRowSet
        
            - A 
CachedRowSet is also called a 
disconnected rowset because it is disconnected
            from a database when it does not need a database connection. It keeps the database connection open only for
            the duration it needs to interact with the database. Once it is done with the connection, it disconnects.
            For example, it connects to a database when it needs to retrieve or update data. 
            - It retrieves all data generated by the command and caches the data in memory. Care should be taken not to
            retrieve a large volume of data in a 
CachedRowSet. Otherwise, it may degrade the performance of
            the application. It provides a new feature called 
paging, which lets you deal with large volume of
            data in chunks. 
            - A 
CachedRowSet is always serializable, scrollable, and updatable. 
            - You can use the following 4 methods to populate data in a 
CachedRowSet: 
            a) 
void execute() throws SQLException 
            b) 
void execute(Connection conn) throws SQLException 
            c) 
void populate(ResultSet data) throws SQLException 
            d) 
void populate(ResultSet rs, int startRow) throws SQLException 
            - What is the actual documentation supposed to be? Is it 
this or 
this????? 
            - 
This Java Doc
            link explains how paging works in 
CachedRowSet. 
            
            - A 
CachedRowSet provides an additional feature called 
paging to let you retrieve rows
            generated by a command in chunks. The chunk of rows that is retrieved at one time is called a 
page.
            You can think of a page as a set of rows, where you decide the number of rows in the set. The maximum number
            of rows in a page is called the 
page size. 
            - Suppose a command for a 
CachedRowSet generates 500 rows. By calling its
            
setPageSize(90), it will retrieve a maximum of 90 rows at a time. When you call its
            
execute() method, it will retrieve the first 90 rows. To retrieve the next 90 rows, you need to
            call its
            
nextPage() method. When it has retrieved five pages (450 rows), calling the
            
nextPage() will retrieve the remaining 50 rows. 
            - It also provides a 
previousPage() method to retrieve the previous page. You can use the
            
nextPage() and 
previousPage() methods of a 
CachedRowSet to retrieve
            and process a large result set in chunks. 
            - Check the code for how to use paging in 
CachedRowSet here.
            
            
            - Code 
here.
            Good luck.
            - You can update the data in a 
CachedRowSet and save the changes back to the database. The
            process of saving changes to the database for a 
CachedRowSet is different from that of a 
JdbcRowSet.
            There are two main reasons to keep the save process a little different for a 
CachedRowSet.
            First, it is disconnected and you do not want to connect to the database often. Second, the updated data may
            have conflicts with the data stored in the database. 
            - The process of inserting, updating, and deleting rows in a 
CachedRowSet is the same as in a
            
JdbcRowSet. But note that these methods do not send changes to the database when used with a
            
CachedRowSet. 
            - After you make changes to a 
CachedRowSet, you can send changes to the database by calling its
            
acceptChanges() method that may commit the changes if you have set the 
commit-on-accept-change
            value to true. You need to refer to the implementation details of the 
CachedRowSet on how it
            lets you set the 
commit-on-accept-change value. If it is set to false, you need to use the
            
commit() or 
rollback() method of the 
CachedRowSet interface to commit
            or rollback changes. 
            - What??? 
            - A 
CachedRowSet has to deal with conflicts that may exist between the data in it and the data
            in the database. For example, you might have retrieved a row from the database, changed the data, and kept
            the changes in the 
CachedRowSet for a long time. When you are ready to save your changes,
            another user might have changed the values for the same rows before you. A 
CachedRowSet uses a
            synchronization provider object to synchronize the changes with the database. It uses another object, a
            synchronization resolver, to resolve any conflicts that it detects during
            the synchronization process. When conflicts are detected during the 
acceptChanges() method
            call, it throws a 
SyncProviderException. You can get the synchronization resolver object that
            is an instance of the 
SyncResolver interface, using the 
getSyncResolver() method
            of the 
SyncProviderException object.
            A 
SyncResolver object lets you navigate through all conflicts and change the values in the rows
            with conflicts to new resolved values. You need to use the 
setResolvedValue() method of a
            
SyncResolver object to set the resolved value when a conflict is detected. 
        
 
        WebRowSet
        
            - TODO: Code Examples 
            - The WebRowSet interface inherits from the CachedRowSet interface. It adds two
            more features to the CachedRowSet:
            reading data and metadata from an XML document, and exporting data and metadata to an XML document. The
            two methods that it adds to provide XML support are readXML() and writeXML().
        
        FilteredRowSet
        
            - TODO: Code Examples 
            - The FilteredRowSet interface inherits from the WebRowSet interface. It provides
            filtering capability to a rowset at the client side. 
            - You can apply a filter to the rowset by using a where clause in its SQL command, which is
            executed in a database. But a FilteredRowSet lets you filter the rows of a rowset after it has
            retrieved the data from a database. 
            - Setting a filter to a rowset does not delete the rows from the rowset. Rather, it lets you access only
            those rows that meet the filter criteria. The filter also applies to inserting, updating, and deleting the
            rows in the rowset. You can only read, insert, update, and delete rows that meet the filter criteria. You
            can reset the filter any time you want to view all rows of a rowset. A filter is an object of a class that
            implements the javax.sql.rowset.Predicate interface.
        
        JoinRowSet
        
            - TODO: Code Examples 
            - The JoinRowSet interface inherits from the WebRowSet interface. It provides the ability to combine (or
            join) two or
            more disconnected rowsets into one rowset. Rows from two or more tables are joined in a query using a SQL
            JOIN.
            A JoinRowSet lets you have a SQL JOIN between two or more rowsets without using a SQL JOIN in a query. 
            - There are 5 standard types of Joins: 
            a) Inner Join 
            b) Left Outer Join 
            c) Right Outer Join 
            d) Full Join 
            e) Cross Join
        
        BatchUpdates
        
            - You saw examples of using the Statement, PreparedStatement, and CallableStatement
            interfaces that let you send one SQL command (or stored procedure call) at a time to the database. The JDBC
            API includes a batch update feature that lets you send multiple update commands to a database in a batch (in
            one bundle) for execution. A batch update greatly improves performance. 
            - The update commands that you can use in a batch update are SQL INSERT, UPDATE,
            DELETE, and stored procedures. A command in a batch should not produce a result set. Otherwise, the JDBC
            driver will throw a SQLException. A command should generate an update count that will indicate
            the number of rows affected in the database by the execution of that command.