What is MyBatis?

MyBatis is an excellent persistence layer framework that supports custom SQL, stored procedures, and advanced mapping. MyBatis eliminates almost all of the JDBC code and the work of setting parameters and fetching result sets. MyBatis can configure and map primitive types, interfaces, and Java POJOs (Plain Old Java Objects) to records in the database via simple XML or annotations.

Using the step

1. The Maven dependencies

<dependency>  
<groupId>org.mybatis</groupId>  <artifactId>mybatis</artifactId> 
<version>x.x.x</version>
</dependency>

Copy the code

Use the mysql driver.

< the dependency > < groupId > mysql < / groupId > < artifactId > mysql connector - Java < / artifactId > < version > 8.0.22 < / version > </dependency>Copy the code

2. Build SqlSessionFactory in XML

The XML configuration file contains the core Settings for the MyBatis system, including the DataSource to get the database connection instance and the TransactionManager to determine the transaction scope and control mode.

<? The 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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>Copy the code

3. Write utility classes

This allows you to create an SqlSessionFactory based on the database connection configuration in the config.xml file and write a method to provide an sqlSession.

public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); }}Copy the code

4. Write entity classes

Write according to data in the database.

public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; }}Copy the code

5. Write the DAO layer

public interface UserDao {
    List<User> getUserList();
}

Copy the code

The original JDBC required writing concrete implementation classes to execute SQL statements. However, Mybatis uses XML definition instead of writing implementation class, named Mapper, where namespace implements interface binding, ID attribute in SELECT is the method in the interface, resultType represents the type of return value, which is the entity class written by us.

<? The 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.zzy.dao.UserDao"> <select id="getUserList" resultType="com.zzy.pojo.User"> select * from mybatis.test </select> </mapper>Copy the code

In this case, you need to specify the path of mapper in config. XML of Mybatis.

6. Write test classes

The utility class MybatisUtils is used to obtain SqlSession, and getMapper performs the configuration in Mapper, which is equivalent to building the implementation class of the interface. Finally, getUserList is called, and the getUserList method is executed to return the query result, and the resource must be closed at last.

@Test
public void test() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList = userDao.getUserList();
    for(User user : userList){
        System.out.println(user);
    }
    sqlSession.close();
}

Copy the code

Life cycle and scope

SqlSessionFactoryBuilder

This class can be instantiated, used, and discarded, and is no longer needed once the SqlSessionFactory has been created. So the best scope for SqlSessionFactoryBuilder instances is the method scope (that is, local method variables). You can reuse SqlSessionFactoryBuilder to create multiple instances of SqlSessionFactory, but it’s best not to keep it around all the time to ensure that all XML parsing resources can be freed up for more important things.

SqlSessionFactory

Once created, the SqlSessionFactory should persist for the duration of the application, and there is no reason to discard it or recreate another instance. The best practice with SqlSessionFactory is not to create the SqlSessionFactory more than once while the application is running. Rebuilding SqlSessionFactory more than once is considered a code “bad habit.” So the best scope for SqlSessionFactory is the application scope. There are many ways to do this, the easiest is to use singleton or static singleton.

SqlSession

Each thread should have its own INSTANCE of SqlSession. An instance of SqlSession is not thread-safe and therefore cannot be shared, so its best scope is the request or method scope. A reference to an SqlSession instance must never be placed in a static domain of a class, or even an instance variable of a class. A reference to an SqlSession instance should also never be placed in any type of managed scope, such as HttpSession in the Servlet framework. If you are currently using a Web framework, consider putting SqlSession in a scope similar to HTTP requests. In other words, each time an HTTP request is received, an SqlSession can be opened, and when a response is returned, it can be closed. The close operation is important, and to ensure that the close operation is performed every time, you should put the close operation ina finally block. The following example is a standard mode to ensure that SqlSession is closed:

Try (SqlSession session. = sqlSessionFactory openSession ()) {/ / your application logic code}Copy the code

If not closed in time, will cause a lot of waste of resources.

Mapper instance

Mappers are interfaces that bind mapping statements. The instance of the mapper interface is obtained from SqlSession. Although technically speaking, the maximum scope of any mapper instance is the same as the SqlSession from which they are requested. But the method scope is the most appropriate scope for the mapper instance. That is, mapper instances should be retrieved in the methods that call them and then discarded after use. The mapper instance does not need to be explicitly closed. Although keeping the mapper instance in the entire request scope is fine, you will soon find that managing too many resources like SqlSession in this scope can get you too busy. Therefore, it is best to place the mapper in the method scope. As in the following example:

try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); // Your application logic code}Copy the code

The XML configuration

enviroments

Mybatis can be configured for multiple environments. This mechanism helps to map SQL to multiple databases, and there are many practical reasons to do so. For example, development, test, and production environments require different configurations; Or you want to use the same SQL mapping across multiple production databases with the same Schema. There are many similar usage scenarios.

But each SQlsessionFactory corresponds to only one environment, and each database corresponds to one SQlsessionFactory.

To specify which environment to create, simply pass it as an optional parameter to SqlSessionFactoryBuilder. The two method signatures that accept the environment configuration are:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

Copy the code

If the specified environment is not specified, the default environment is used. Default = environment ID is the default environment.

transactionManager

In MyBatis there are two types of transaction manager (i.e., type = “[JDBC | MANAGED]”)

● JDBC – This configuration directly uses JDBC’s commit and rollback facilities, which rely on connections obtained from data sources to manage transaction scopes. ● MANAGED – This configuration does little. It never commits or rolls back a connection, but lets the container manage the entire life cycle of the transaction (such as the context of the JEE application server). It closes the connection by default. However, some containers do not want the connection to be closed, so you need to set the closeConnection property to false to prevent the default closing behavior. Such as:

<transactionManager type="MANAGED">  
<property name="closeConnection" value="false"/>
</transactionManager>

Copy the code

dataSource

The dataSource element uses the standard JDBC dataSource interface to configure the resources of the JDBC connection object.

There are three kinds of built-in data types (type = “[UNPOOLED | POOLED | JNDI]”) :

  • UNPOOLED – The implementation of this data source opens and closes connections on each request. Although a bit slow, it is a good choice for simple applications that do not require high availability of database connections.
  • POOLED – This data source implementation leverages the concept of “pooling” to organize JDBC connection objects, avoiding the initialization and authentication time required to create new connection instances. This is a popular way to allow concurrent Web applications to respond quickly to requests.
  • JNDI – This data source implementation is intended for use in a container such as an EJB or application server, which can centrally or externally configure the data source and then place a data source reference to the JNDI context.

Properties

Properties can be configured by writing a.properties file, or declared directly in the environment. If you use.properties, you need to specify a path in the properties resource, and then when you use properties in the environment, ${} corresponds to the name of the property in the. Properties file.

If you configure both. Properties and in an XML file, then. Properties is preferred.

typeAliases

A type alias sets an abbreviated name for a Java type. It is only used for XML configuration and is intended to reduce redundant fully qualified class name writing. There are two ways to declare:

Give each class a specific alias. The advantage of this approach is that you can specify your own alias.

Scan a class under the package and automatically alias the class. The default alias is the first letter of the class’s lowercase name. For example, if the Alias of com.blog.User is User, you can add an Alias annotation to the class or implement a custom Alias.

@Alias("author")
public class Author {
    ...
}

Copy the code

Settings

These are very important tuning Settings in MyBatis, and they change the runtime behavior of MyBatis. The following table describes the meanings and default values of the Settings.

Main knowledge:

log4j

Log4j is an open source project of Apache. By using Log4j, you can control the destination of log messages to the console, files, GUI components, even the socket server, NT event logger, UNIX Syslog daemon, etc. We can also control the output format of each log; By defining the level of each log message, we can more carefully control the log generation process. The most interesting thing is that these can be configured flexibly through a configuration file without the need to modify the application code.

Step 1: Import the JAR package in Maven

<! -- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> < artifactId > log4j < / artifactId > < version > 1.2.17 < / version > < / dependency >Copy the code

Step 2: Write the log4j.properties file

log4j.rootLogger=DEBUG,console,file

log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n

log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/zzy.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

log4j.appender.org.mybatis=DEBUG
log4j.appender.java.sql=DEBUG
log4j.appender.java.sql.Statement=DEBUG
log4j.appender.java.sql.ResultSet=DEBUG
log4j.appender.java.sql.PreparedStatement=DEBUG

Copy the code

Step 3: Configure

Be sure to be identical. Differences in case, space, and so on can cause errors.

Step 4: Get the object of the currently used class.

static Logger logger = Logger.getLogger(xxx.class);

Copy the code

Effect:

Mappers

Now that the behavior of MyBatis has been configured by the above elements, it is time to define the SQL mapping statement. But first, we need to tell MyBatis where to find these statements.

XML mapping file

Parameter mapping

Since parameterType is automatically set to int, this parameter can be named however you like.

If a parameter object of type User is passed into the statement, the ID, USERNAME, and Password attributes are looked for and their values are passed into the preprocessed statement’s parameters.

<insert id="insertUser" parameterType="User">  
insert into users (id, username, password)  
values (#{id}, #{username}, #{password})
</insert>

Copy the code

Tip: JDBC requires that the JDBC type (jdbcType) be specified if a column allows null values and will use arguments with null values.

While these options are powerful, most of the time you simply specify the attribute name, at most the jdbcType for columns that might be empty, and MyBatis will figure out the rest.

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

Copy the code

Using @param, you can specify the name of the argument that mapper receives.

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column,
@Param("value") String value);

Copy the code

Results the mapping

The real power of MyBatis lies in its statement mapping, which is its magic. Because of its exceptional power, the MAPper’s XML file is relatively simple. If you compare it to JDBC code with the same functionality, you’ll immediately see that nearly 95% of the code is saved. MyBatis aims to reduce usage costs and allow users to focus more on SQL code.

resultMap

The ResultMap element is the most important and powerful element in MyBatis. It frees you from 90% of the JDBC ResultSet data extraction code and in some cases allows you to do things that ARE not supported by JDBC. In fact, when writing mapping code for complex statements such as joins, a ResultMap can replace thousands of lines of code that do the same thing. ResultMap is designed to achieve zero configuration for simple statements, while complex statements only need to describe the relationship between statements.

<select id="selectUsers" resultMap="userResultMap">  
select user_id, user_name, hashed_password  from some_table  where id = #{id}
</select>

<resultMap id="userResultMap" type="User">  
<id property="id" column="user_id" />  
<result property="username" column="user_name"/>  
<result property="password" column="hashed_password"/>
</resultMap>

Copy the code

The nice thing about ResultMap – you don’t need to explicitly configure them at all, when the attribute names of the entity class are the same as the column names of the database, you don’t need to use them at all, you just need to configure them when they are different.

Dynamic SQL

if

The most common scenario for using dynamic SQL is to include part of a WHERE clause based on criteria. Such as:

< select id = "findActiveBlogWithTitleLike" resultType = "Blog" > select * FROM Blog WHERE state = 'ACTIVE' < if test = "title! = null"> AND title like #{title} </if> </select>Copy the code

What if you want an optional search with the “title” and “author” parameters? First, I want to change the statement name to something more appropriate; Now, all you need to do is add another condition.

<select id="findActiveBlogLike" resultType="Blog"> select * FROM Blog WHERE state = 'ACTIVE' <if test="title! = null"> AND title like #{title} </if> <if test="author ! = null and author.name ! = null"> AND author_name like #{author.name} </if> </select>Copy the code

Choose, when, or otherwise

Sometimes, we don’t want to use all of the conditions, but just choose one of several conditions to use. For this, MyBatis provides the Choose element, which is a bit like the Switch statement in Java.

<select id="findActiveBlogLike" resultType="Blog"> select * FROM Blog WHERE state = 'ACTIVE' <choose> <when test="title" ! = null"> AND title like #{title} </when> <when test="author ! = null and author.name ! = null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose> </select>Copy the code

Trim, WHERE, set

SQL > select * from blog where SQL > select * from blog where SQL > select * from blog where SQL > select * from blog where SQL > Select * from blog where title like #{title}; select * from blog where title like #{title}; Clearly wrong statement.

<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE <if test="state ! = null"> state = #{state} </if> <if test="title ! = null"> AND title like #{title} </if> <if test="author ! = null and author.name ! = null"> AND author_name like #{author.name} </if> </select>Copy the code

MyBatis has a simple solution that works for most scenarios. In other scenarios, you can customize it to fit your needs. This, however, requires a simple change:

<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state ! = null"> state = #{state} </if> <if test="title ! = null"> AND title like #{title} </if> <if test="author ! = null and author.name ! = null"> AND author_name like #{author.name} </if> </where> </select>Copy the code

The WHERE element inserts the “where” clause only if the child element returns anything. Also, if the clause begins with “AND” OR “OR,” the WHERE element removes those as well.

You can also customize the functionality of the WHERE element by customizing the trim element if the where element is different from what you expected. For example, the custom trim element equivalent to the WHERE element is:

<trim prefix="WHERE" prefixOverrides="AND |OR ">  
...
</trim>

Copy the code

The prefixOverrides property ignores sequences of text separated by pipe characters (note that Spaces are required in this example). The above example removes all the content specified in the prefixOverrides attribute and inserts the content specified in the prefix attribute.

A similar solution for dynamically updated statements is called SET. The set element can be used to dynamically contain columns that need to be updated, ignoring other columns that are not updated. Such as:

<update id="updateAuthorIfNecessary"> update Author <set> <if test="username ! = null">username=#{username}, </if> <if test="password ! = null">password=#{password}, </if> <if test="email ! = null">email=#{email}, </if> <if test="bio ! = null">bio=#{bio} </if> </set> where id=#{id} </update>Copy the code

In this example, the set element inserts the set keyword dynamically at the beginning of the line and removes additional commas (which are introduced when a conditional statement is used to assign values to a column).

Take a look at the custom trim element equivalent to the set element:

<trim prefix="SET" suffixOverrides=",">  
...
</trim>

Copy the code

foreach

Another common use of dynamic SQL is traversal of collections (especially when building IN conditional statements). Such as:

<select id="selectPostIn" resultType="domain.blog.Post">  SELECT *  FROM POST P WHERE ID in  
<foreach item="item" index="index" collection="list"     open="(" separator="," close=")">  

   #{item}  

</foreach>
</select>

Copy the code

The foreach element is very powerful. It allows you to specify a collection and declare the collection item and index variables that can be used inside the element. It also allows you to specify beginning and ending strings and separators between iterations of collection items. This element also doesn’t add extra delimiters by mistake, look how smart it is!

Tip: You can pass foreach any iterable (List, Set, etc.), Map, or array object as a collection parameter. When using an iterable or array, index is the sequence number of the current iteration and the value of item is the element fetched in the current iteration. When using a Map object (or a collection of Map.Entry objects), index is the key and item is the value.

bind

<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT  * FROM BLOG WHERE title LIKE #{pattern} </select>Copy the code

The cache

The basic concept

MyBatis has a powerful built-in transactional query caching mechanism that can be easily configured and customized. To make it more powerful and easy to configure, we have made a number of improvements to the caching implementation in MyBatis 3.

By default, only local session caching is enabled, which only caches data in one session. To enable level 2 caching globally, just add a line to your SQL mapping file:

<cache/>

Copy the code

You also need to declare cacheEnable in Settings, although this is true by default.

That’s basically it. This simple statement has the following effect:

  • The results of all SELECT statements in the mapping statement file will be cached.
  • All INSERT, UPDATE, and DELETE statements in the mapping statement file flush the cache.
  • The cache uses the Least Recently Used algorithm (LRU) algorithm to clear unwanted caches.
  • The cache is not flushed regularly (that is, there are no flush intervals).
  • The cache holds 1024 references to lists or objects (whichever is returned by the query method).
  • The cache is treated as a read/write cache, which means that the retrieved object is not shared and can be safely modified by the caller without interfering with potential changes made by other callers or threads.

Tip: The cache only works on statements in the mapping file where the cache label resides. If you use a mix of Java APIS and XML mapping files, statements in the common interface will not be cached by default. You need to specify the cache scope using the @Cachenamespaceref annotation.

These attributes can be modified through the attributes of the cache element.

<cache  eviction="FIFO"  flushInterval="60000"  size="512"  readOnly="true"/>

Copy the code

This more advanced configuration creates a FIFO cache, refreshed every 60 seconds, that can store up to 512 references to result objects or lists, and the returned objects are considered read-only, so modifying them could cause conflicts among callers in different threads.

The cleanup policies available are:

The default cleanup policy is LRU.

The flushInterval property can be set to any positive integer, and the value should be a reasonable amount of time in milliseconds. The default is no, that is, there is no refresh interval, and the cache is flushed only when the statement is called.

The size attribute can be set to any positive integer, taking into account the size of the object to be cached and the memory resources available in the runtime environment. The default value is 1024.

The readOnly property can be set to true or false. A read-only cache returns the same instance of the cache object to all callers. Therefore, these objects cannot be modified. This provides a significant performance boost. A read-write cache returns (through serialization) a copy of the cached object. It’s slower, but safer, so the default is false.

The working process

Level 2 cache dirty read

Since the level 2 cache works at the Namesace level, that is, different mappers have their own set of caches, dirty reads can occur when different mappers operate on the same table.

If mapper A does not perform update, delete, or INSERT operations during this period, mapper A’s cache will not be refreshed. When Mapper A selects data again, the data obtained by Mapper A is dirty because of the working mechanism of Mybatis cache.

Therefore, after the level 2 cache is enabled, all the additions, deletions, changes and searches of the same table should be under the same Mapper.

Mybatis executes the process

  1. Resource Reads the configuration file

  2. Parses the configuration file flow XML

  3. Configuration

  4. Real SqlSessionFactory column

  5. transactionManager

  6. executor

  7. Create a SqlSession

  8. To realize the CRUD

conclusion

Thank you for reading here, the article has any shortcomings please correct, feel the article is helpful to you remember to give me a thumbs up, every day will share Java related technical articles or industry information, welcome to pay attention to and forward the article!

Welcome to pay attention to the public number: the future has light, receive a line of large factory Java interview questions summary + the knowledge points learning thinking guide + a 300 page PDF document Java core knowledge points summary!