Skip to content

Database

Here is a simple DB schema that will be used for this sample: db_schema We will implement mapping for Users and UserPreferences tables.

Dependencies

For integration with DB, we recommend to use MyBatis ORM framework. MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. To start with, let's add the required dependencies into Maven pom.xml:

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.2</version>
</dependency>
<!-- Postgres driver -->
<dependency> 
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
   <version>42.2.6</version>
</dependency>
<!-- MySQL driver -->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.17</version>
</dependency>

Mappers

The next step is MyBatis mappers implementation, read the official documentation to understand all the details. Let's place all the mappers into src/main/resources/mappers. Here is a UserMapper.xml sample:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zebrunner.carina.demo.db.mappers.UserMapper">

    <insert id="create" useGeneratedKeys="true" keyProperty="id">
        <![CDATA[
            INSERT INTO carina.USERS (USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL, STATUS)
            VALUES (
                #{username},
                #{password},
                #{firstName},
                #{lastName},
                #{email},
                #{status}
            )
        ]]>
    </insert>

    <sql id="getUser">
        <![CDATA[
            SELECT
                U.ID AS USER_ID,
                U.USERNAME AS USER_USERNAME,
                U.PASSWORD AS USER_PASSWORD,
                U.FIRST_NAME AS USER_FIRST_NAME,
                U.LAST_NAME AS USER_LAST_NAME,
                U.EMAIL AS USER_EMAIL,
                U.STATUS AS USER_STATUS,
                UP.ID AS USER_PREFERENCE_ID,
                UP.NAME AS USER_PREFERENCE_NAME,
                UP.VALUE AS USER_PREFERENCE_VALUE,
                UP.USER_ID AS USER_PREFERENCE_USER_ID
            FROM
                carina.USERS U
            LEFT JOIN
                carina.USER_PREFERENCES UP
            ON
                UP.USER_ID = U.ID
        ]]>
    </sql>

    <select id="findById" resultMap="UserResultMap">
        <include refid="getUser" />
        <![CDATA[
            WHERE U.ID = #{id};
        ]]>
    </select>

    <select id="findByUserName" resultMap="UserResultMap">
        <include refid="getUser" />
        <![CDATA[
            WHERE U.USERNAME = #{username};
        ]]>
    </select>

    <update id="update">
        <![CDATA[
            UPDATE
                carina.USERS
        ]]>
        <set>
            <if test="null != firstName">
                    <![CDATA[
                       FIRST_NAME = #{firstName},
                    ]]>
            </if>
            <if test="null != lastName">
                    <![CDATA[
                       LAST_NAME = #{lastName},
                    ]]>
            </if>
            <if test="null != email">
                    <![CDATA[
                       EMAIL = #{email},
                    ]]>
            </if>
            <if test="null != username">
                    <![CDATA[
                       USERNAME = #{username},  
                    ]]>
            </if>
            <if test="null != password">
                    <![CDATA[
                       PASSWORD = #{password},  
                    ]]>
            </if>
        </set>
        <![CDATA[
            WHERE
                ID = #{id}
        ]]>
    </update>

    <delete id="delete">
        <![CDATA[
            DELETE FROM carina.USERS
            WHERE ID = #{id}
        ]]>
    </delete>

    <resultMap type="com.zebrunner.carina.demo.db.models.User"
        id="UserResultMap" autoMapping="false">
        <id column="USER_ID" property="id" />
        <result column="USER_USERNAME" property="username" />
        <result column="USER_PASSWORD" property="password" />
        <result column="USER_FIRST_NAME" property="firstName" />
        <result column="USER_LAST_NAME" property="lastName" />
        <result column="USER_EMAIL" property="email" />
        <result column="USER_STATUS" property="status" />
        <collection property="preferences"
            ofType="com.zebrunner.carina.demo.db.models.UserPreference"
            resultMap="com.zebrunner.carina.demo.db.mappers.UserPreferenceMapper.UserPreferenceResultMap" />
    </resultMap>

</mapper>

Now we have to implement a DB domain object User class which reflects the DB table structure:

package com.zebrunner.carina.demo.db.models;

import java.util.ArrayList;
import java.util.List;

public class User {

    private Long id;
    private String username;
    private String password;
    private String email;
    private String firstName;
    private String lastName;
    private Status status;
    private List<UserPreference> preferences = new ArrayList<>();

    public User() {
    }

    public enum Status {
        ACTIVE, INACTIVE
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public List<UserPreference> getPreferences() {
        return preferences;
    }

    public void setPreferences(List<UserPreference> preferences) {
        this.preferences = preferences;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }
}

Also, we have to describe the mapper interface, UserMapper interface:

package com.zebrunner.carina.demo.db.mappers;

import com.zebrunner.carina.demo.db.models.User;

public interface UserMapper {

    void create(User user);

    User findById(long id);

    User findByUserName(String username);

    void update(User user);

    void delete(User user);

}

Configuration

First of all, we need to place DB credentials into src/main/resources/_database.properties:

#===============================================================#
#================== Database configuration ====================#
#===============================================================#
db.url=jdbc:postgresql://localhost:5432/postgres
db.driver=org.postgresql.Driver
db.user=postgres
db.pass=postgres

#db.url=jdbc:mysql://localhost:3306/mysql
#db.driver=com.mysql.jdbc.Driver
#db.user=mysql
#db.pass=mysql

All the mappers and reference to _database.properties should be specified in src/main/resources/mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="_database.properties" />

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}" />
                <property name="url" value="${db.url}" />
                <property name="username" value="${db.user}" />
                <property name="password" value="${db.pass}" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mappers/UserMapper.xml" />
        <mapper resource="mappers/UserPreferenceMapper.xml" />
    </mappers>
</configuration>

Connection factory

Connection factory is responsible for MyBatis context initialization and creation of mapper instances, take a look at the sample implementation of ConnectionFactory:

package com.zebrunner.carina.demo.utils;

import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.zebrunner.carina.demo.db.mappers.UserMapper;
import com.zebrunner.carina.demo.db.mappers.UserPreferenceMapper;

public class ConnectionFactory {
    private static SqlSessionFactory factory;

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return factory;
    }

    public static UserMapper getUserMapper() {
        return ConnectionFactory.getSqlSessionFactory().openSession(true).getMapper(UserMapper.class);
    }

    public static UserPreferenceMapper getUserPreferenceMapperMapper() {
        return ConnectionFactory.getSqlSessionFactory().openSession(true).getMapper(UserPreferenceMapper.class);
    }
}

Usage sample

Finally, DBSampleTest illustrates the usage of MyBatis in tests:

package com.zebrunner.carina.demo;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

import com.zebrunner.carina.core.IAbstractTest;
import com.zebrunner.agent.core.annotation.TestLabel;
import org.apache.ibatis.session.SqlSession;
import org.testng.annotations.Test;
import com.zebrunner.carina.demo.db.mappers.UserMapper;
import com.zebrunner.carina.demo.db.mappers.UserPreferenceMapper;
import com.zebrunner.carina.demo.db.models.User;
import com.zebrunner.carina.demo.db.models.User.Status;
import com.zebrunner.carina.demo.utils.ConnectionFactory;
import com.zebrunner.carina.demo.db.models.UserPreference;

/**
 * This sample shows how create DB test.
 * 
 * @author qpsdemo
 */
public class DBSampleTest implements IAbstractTest {

    ...

    @Test
    @TestLabel(name = "feature", value = "database")
    public void createUser() {
        try (SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession(true)) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            userMapper.create(USER);
            checkUser(userMapper.findById(USER.getId()));
        }
    }

    @Test(dependsOnMethods = "createUser")
    @TestLabel(name = "feature", value = "database")
    public void createUserPreference() {
        try (SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession(true)) {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            UserPreferenceMapper userPreferenceMapper = session.getMapper(UserPreferenceMapper.class);
            USER_PREFERENCE.setUserId(USER.getId());
            userPreferenceMapper.create(USER_PREFERENCE);
            checkUserPreference(userMapper.findById(USER.getId()).getPreferences().get(0));
        }
    }

    ... 

}