Genreator 生成代码
引入pom文件
GenreatorConfig.xml
去掉注释 suppressAllComments
执行mvn mybatis-generator.
写POJO和一个Mapper用@Select()写SQL实现
对比
优点:跟接口分离,统一管理;复杂的语句可以不影响接口的可读性
缺点:过多的xml文件
Annotation
接口就能看到SQL,可读性高,不需要再去找xml文件;联合查询不好维护,代码可读性差;SQL复用差
Scope
SqlSessionFactoryBuilder method
SqlSessionFactory application
SqlSession request/method (可以认为是线程级)
Mapper method
environment 可以定义不同的环境 SqlSessionFactoryBuilder.builder(InputStream inputStream, String environment),根据不同的环境切换不同的连接
type handler 自定义typeHandler,自定义的类型会覆盖原有的typeHandler
plugin
每当 mybatis 在准备语句上设置参数或从结果集检索值时, 都会使用 typehandler 以适合 java 类型的方式检索该值。下表描述了默认的类型处理程序。
TypeHandlers列表
Type Handler | Java Types | JDBC Types |
BooleanTypeHandler |
java.lang.Boolean , boolean |
Any compatible BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
Any compatible NUMERIC or BYTE |
ShortTypeHandler |
java.lang.Short , short |
Any compatible NUMERIC or SHORT INTEGER |
IntegerTypeHandler |
java.lang.Integer , int |
Any compatible NUMERIC or INTEGER |
LongTypeHandler |
java.lang.Long , long |
Any compatible NUMERIC or LONG INTEGER |
FloatTypeHandler |
java.lang.Float , float |
Any compatible NUMERIC or FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
Any compatible NUMERIC or DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
Any compatible NUMERIC or DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
ClobReaderTypeHandler |
java.io.Reader |
- |
ClobTypeHandler |
java.lang.String |
CLOB , LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR , NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
BlobInputStreamTypeHandler |
java.io.InputStream |
- |
ByteArrayTypeHandler |
byte[] |
Any compatible byte stream type |
BlobTypeHandler |
byte[] |
BLOB , LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
DateOnlyTypeHandler |
java.util.Date |
DATE |
TimeOnlyTypeHandler |
java.util.Date |
TIME |
SqlTimestampTypeHandler |
java.sql.Timestamp |
TIMESTAMP |
SqlDateTypeHandler |
java.sql.Date |
DATE |
SqlTimeTypeHandler |
java.sql.Time |
TIME |
ObjectTypeHandler |
Any | OTHER , or unspecified |
EnumTypeHandler |
Enumeration Type | VARCHAR any string compatible type, as the code is stored (not index). |
EnumOrdinalTypeHandler |
Enumeration Type | Any compatible NUMERIC or DOUBLE , as the position is stored (not the code itself). |
InstantTypeHandler |
java.time.Instant |
TIMESTAMP |
LocalDateTimeTypeHandler |
java.time.LocalDateTime |
TIMESTAMP |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
LocalTimeTypeHandler |
java.time.LocalTime |
TIME |
OffsetDateTimeTypeHandler |
java.time.OffsetDateTime |
TIMESTAMP |
OffsetTimeTypeHandler |
java.time.OffsetTime |
TIME |
ZonedDateTimeTypeHandler |
java.time.ZonedDateTime |
TIMESTAMP |
YearTypeHandler |
java.time.Year |
INTEGER |
MonthTypeHandler |
java.time.Month |
INTEGER |
YearMonthTypeHandler |
java.time.YearMonth |
VARCHAR or LONGVARCHAR |
JapaneseDateTypeHandler |
java.time.chrono.JapaneseDate |
DATE |
MyBatis TypeHandler (xml定义 或者 java config注入 或者@Annotation)
示例Demo
// 创建自己的Handler,并定义MappedJdbcTypes
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MytypeHander extends BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter + " i love you");
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName) + " 尚先生";
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex) + " 尚先生";
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex) + " 尚先生";
}
}
// 创建自己的Handler,并定义MappedJdbcTypes
public static SqlSession getSqlSession() throws FileNotFoundException {
//配置文件
InputStream configFile = new FileInputStream( "D:\\MyWorkSpace\\mybatis\\src\\main\\java\\com\\sxs\\mybatis\\demo\\mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(configFile); //加载配置文件得到SqlSessionFactory
return sqlSessionFactory.openSession();
}
public static void main(String[] args) throws FileNotFoundException {
SqlSession sqlSession = getSqlSession();
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
long start = System.currentTimeMillis();
Test test = testMapper.selectByPrimaryKey(id);
System.out.println("cost:" + (System.currentTimeMillis() - start)); }
// 添加TypeHandler之前查询运行结果
cost:327 Test(id=2, nums=200, name=魅族)
// 添加TypeHandler之后查询运行结果
cost:409 Test(id=2, nums=200, name=魅族 尚先生
MyBatis Plugin
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
为了减少数据库的压力,防止大量的请求直接打在DB上,造成DB挂掉,或性能急剧下降而引发的一系列问题;
为了减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
判断从Cache中根据特定的key值取的数据数据是否为空,即是结果是否存在;
实现的本质:调用JDBC的时候,传入的SQL语句要完全相同,传递给JDBC的参数值也要完全相同。
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
1541993073636
close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
clearCache()方法,会清空PerpetualCache对象中的数据,但是该对象仍可使用;
update()、delete()、insert()方法,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
Annotation
xml文件开启
自动提交设置为false,正常提交,异常时回滚。
SqlSession\Mapper 生命周期管理
示例Demo
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private String name;
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String str = (String) properties.get("someProperty");
this.name = str;
System.out.println(str);
}
}
public static boolean insert(Test test) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "test", "123456");
// 自动提交设置为false
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement("INSERT INTO test VALUES (?,?,?)");
if (null != test.getId()) {
preparedStatement.setInt(1, test.getId());
} else {
preparedStatement.setNull(1, INTEGER);
}
preparedStatement.setInt(2, test.getNums());
preparedStatement.setString(3, test.getName());
preparedStatement.execute();
connection.commit();
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (null != connection) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return true;
}
SqlSession --> Executor --> Transaction --> Connection
之后就是JDBC的处理逻辑
mapper是怎么注入进来的
XML 或者 java Config- ->MapperScannerConfigurer
--> BeanDefinitionRegistryPostProcessor
-->BeanFactoryPostProcessor
告诉Spring去加载MyBatisConfig
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
Annotation --> MapperScannerRegistrar
-->ImportBeanDefinitionRegistrar
告诉Spring去加载MyBatisConfig
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}
sqlsession
的生命周期在没有@Transaction
的情况下是request/method级别的;在有@Transaction
的情况下
是@Transaction
范围内的。
Mapper
在没有spring的时候是Method
级别的,在有的时候是全局的Singleton
。
// set
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
// get
Configuration configuration = getSqlSession().getConfiguration();
org.apache.ibatis.executor.resultset.DefaultResultSetHandler # handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext
拼SQL
select * from table limit 1000, 10;
插件
mybatis PageHelper https://github.com/pagehelper/Mybatis-PageHelper/tree/master/src/main/java/com/github/pagehelper
自定义拦截器实现分页功能,拦截点如上述拦截器描述
mybatis 允许您在映射语句执行过程中的某些点拦截对调用。默认情况下, mybatis 允许插件拦截方法调用:
MyBatis深入理解和使用-MyBatis缓存体系:https://blog.csdn.net/shang_xs/article/details/86656353
MyBatis深入理解和使用-MyBatis事务管理:https://blog.csdn.net/shang_xs/article/details/86656649