I am developing a java web project using Spring
and Mybatis
.
In the dao level, I defined a super class and a super interface which implemented all common methods.
Thus when create sub class or interface for a specific model in dao level, I only need to implement the super dao class & interface, and left the class body and interface body empty.
Over half of the sub dao level class & interface is empty through all the time.
(Example of the empty dao class & interface:)
RoleDao.java
package core.dao;
import core.dao.base.BaseDao;
import core.model.Role;
public interface RoleDao extends BaseDao<Role> {
}
RoleDaoImpl.java
package core.dao.impl;
import org.springframework.stereotype.Repository;
import core.dao.RoleDao;
import core.dao.base.BaseDaoImpl;
import core.model.Role;
@Repository
public class RoleDaoImpl extends BaseDaoImpl<Role> implements RoleDao {
}
My question is:
Is there a good way to avoid writing these empty class & interface, while still could use them?
I am thinking of using Code generator
to generate these class file, or use Java reflection
to create such class & interface at runtime as need, but didn't get into detail yet.
@Update
It seems not flexible to achieve the target without creating source code, so I decided to write some simple java source code generator for java web project.
And a tool called codemodel
is very suitable to do that, it is developed by Sun, and now owned by Oracle I guess.
And, I gave an answer by myself with code that I wrote to generate java source code.
The Repository classes for the classes in our projects that use QueryDSL and JPA only have an interface, but not an implementation. However, it does not answer the question whether it is possible to directly generate these repositories based on the entity classes, although it would be similar to what the Apt Maven Plugin does to create the QEntity
classes for use with QueryDSL.
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
}
@Repository
public interface DummyDataRepository extends BaseRepository<DummyData, Long> {
}
About a month ago I was asking myself the same thing :) So, seems that we have a kind of solution, since you are using Spring library. As I read on docs:
Rather than code data access objects (DAOs) manually using SqlSessionDaoSupport or SqlSessionTemplate, Mybatis-Spring provides a proxy factory: MapperFactoryBean. This class lets you inject data mapper interfaces directly into your service beans. When using mappers you simply call them as you have always called your DAOs, but you won't need to code any DAO implementation because MyBatis-Spring will create a proxy for you.
There's an example on GitHub and also on this MyBatis' page.
I hope that it gives you some insights, because maybe it isn't feasible refactoring your whole system to be benefited of such nice feature.
I just wrote a simple code generator for my project.
It's just a single class, and could generate model/dao/service/action level code template for 1 or more models in a single execution.
Dependence:
It use codemodel
and apache commons-io
lib, and it's a spring
+ springMVC
project.
How to use it:
It import some base class/interface in my project, from which the generated class extends/implements from, so you might can't run it directly. But you can create them as empty class/interface, or remove them from the genSourceXxx() function.
CodeGenerator.java:
package my.project.util;
import my.project.dao.base.BaseDao;
import my.project.dao.base.BaseDaoImpl;
import my.project.model.base.BaseIdModel;
import my.project.service.base.BaseService;
import my.project.service.base.BaseServiceImpl;
import my.project.web.action.base.BaseAction;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
/**
* code generator
*
* @author eric
* @date Apr 10, 2015 3:32:57 PM
*/
public class CodeGenerator {
// location of source folder
public static final String tmpSourceFolderBaseLocation = "/tmp/java_code/"; // tmp location for generated code,
public static final String actualSourceFolderBaseLocation = "/mnt/star/workplace/eclipse_j2ee_workplace/project-name/source/java/"; // actual source folder,
// package
public static final String packageSeparator = ".";
public static final String basePackage = "my.project";
public static final String modelPackage = "model";
public static final String daoPackage = "dao";
public static final String daoImplPackage = "dao.impl";
public static final String servicePackage = "service";
public static final String serviceImplPackage = "service.impl";
public static final String actionPackage = "web.action";
// source file path
public static final String pkgPathSeparator = File.separator;
public static final String sourceSuffix = ".java";
public static final String basePkgPath = "my/project";
public static final String modelPkgPath = "model";
public static final String daoPkgPath = "dao";
public static final String daoImplPkgPath = "dao" + pkgPathSeparator + "impl";
public static final String servicePkgPath = "service";
public static final String serviceImplPkgPath = "service" + pkgPathSeparator + "impl";
public static final String actionPkgPath = "web" + pkgPathSeparator + "action";
// naming
public static final String daoSuffix = "Dao";
public static final String daoImplSuffix = "DaoImpl";
public static final String serviceSuffix = "Service";
public static final String serviceImplSuffix = "ServiceImpl";
public static final String actionSuffix = "Action";
// compiler for generated source code,
public static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// classloader for compiled class,
public static final ClassLoader cl = genCl(tmpSourceFolderBaseLocation);
/**
* compile a source file,
*
* @param sourcePath
* @throws MalformedURLException
*/
public static void compileSource(String sourcePath) throws MalformedURLException {
// set this so that won't get compile error,
System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + tmpSourceFolderBaseLocation);
compiler.run(null, null, null, sourcePath);
}
/**
* generate a classloader,
*
* @param path
* @return
* @throws MalformedURLException
*/
public static ClassLoader genCl(String path) {
ClassLoader cl = null;
try {
cl = new URLClassLoader(new URL[] { new File(path).toURI().toURL() });
} catch (MalformedURLException e) {
e.printStackTrace();
}
return cl;
}
/**
* <p>
* Generate source for model.
* </p>
*
* @param modelName
* @throws IOException
* @throws JClassAlreadyExistsException
*/
public static void genSourceModel(String modelName) throws IOException, JClassAlreadyExistsException {
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(modelFullName, ClassType.CLASS);
// extends
dc._extends(BaseIdModel.class);
// id
JFieldVar idField = dc.field(JMod.PRIVATE, Integer.class, "id"); // field
// id - getter method
JMethod getIdMethod = dc.method(JMod.PUBLIC, Integer.class, "getId");
getIdMethod.body()._return(idField);
getIdMethod.annotate(cm.ref(Override.class)); // annotation - override
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(modelPkgPath, modelName));
}
public static void genSourceDao(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(daoFullName, ClassType.INTERFACE);
// extends
JClass superClazz = cm.ref(BaseDao.class).narrow(cl.loadClass(modelFullName));
dc._extends(superClazz);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(daoPkgPath, modelName, daoSuffix));
}
public static void genSourceDaoImpl(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String daoImplFullName = genFullName(daoImplPackage, modelName, daoImplSuffix);
String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
String modelFullName = genFullName(modelPackage, modelName);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(daoImplFullName, ClassType.CLASS);
dc.annotate(Repository.class);
// extends
JClass superClazz = cm.ref(BaseDaoImpl.class).narrow(cl.loadClass(modelFullName));
dc._extends(superClazz);
// implements
dc._implements(cl.loadClass(daoFullName));
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(daoImplPkgPath, modelName, daoImplSuffix));
}
public static void genSourceService(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(serviceFullName, ClassType.INTERFACE);
// extends
dc._extends(BaseService.class);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(servicePkgPath, modelName, serviceSuffix));
}
public static void genSourceServiceImpl(String modelName, boolean serviceTransaction) throws JClassAlreadyExistsException, ClassNotFoundException,
IOException {
String serviceImplFullName = genFullName(serviceImplPackage, modelName, serviceImplSuffix);
String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(serviceImplFullName, ClassType.CLASS);
// annotation
dc.annotate(Service.class);
if (serviceTransaction) {
dc.annotate(Transactional.class);
}
// extends
dc._extends(BaseServiceImpl.class);
// implements
dc._implements(cl.loadClass(serviceFullName));
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(serviceImplPkgPath, modelName, serviceImplSuffix));
}
public static void genSourceAction(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
genSourceAction(modelName, null);
}
/**
* generate action,
*
* @param modelName
* @param rootMappingPath
* root mapping path, if null or empty then don't have this annotation,
* @throws JClassAlreadyExistsException
* @throws ClassNotFoundException
* @throws IOException
*/
public static void genSourceAction(String modelName, String rootMappingPath) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
String actionFullName = genFullName(actionPackage, modelName, actionSuffix);
JCodeModel cm = new JCodeModel();
// define type,
JDefinedClass dc = cm._class(actionFullName, ClassType.CLASS);
// annotation
dc.annotate(Controller.class);
if (StringUtils.isNotBlank(rootMappingPath)) {
dc.annotate(cm.ref(RequestMapping.class)).param("value", rootMappingPath);
}
// extends
dc._extends(BaseAction.class);
// generate source code,
cm.build(new File(tmpSourceFolderBaseLocation));
// compile
compileSource(genFullPath(actionPkgPath, modelName, actionSuffix));
}
/**
* <p>
* generate a serial java source code base on a single model, don't include service level,
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* @param modelName
*/
public static void genStack(String modelName) {
genStack(modelName, false, false, null);
}
/**
* <p>
* generate a serial java source code base on a single model.
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* @param modelName
* @param includeService
* specify whether include service level,
* @param serviceTransaction
* whether add transaction annotation to service impl class,
* @param actionRootMappingPath
* root mapping path, if null or empty then don't have this annotation,
*/
public static void genStack(String modelName, boolean includeService, boolean serviceTransaction, String actionRootMappingPath) {
try {
initTmp(); // clean or create folder,
// generate code - start
genSourceModel(modelName);
genSourceDao(modelName);
genSourceDaoImpl(modelName);
if (includeService) {
genSourceService(modelName);
genSourceServiceImpl(modelName, serviceTransaction);
}
genSourceAction(modelName, actionRootMappingPath);
// generate code - end
merge(); // copy,
initTmp(); // clean, so that won't have duplicated class,
} catch (IOException | JClassAlreadyExistsException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* <p>
* batch generate.
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*
* @param models
* map of "modelName : actionRootMappingPath"
* @param includeService
* specify whether include service level,
* @param serviceTransaction
* whether add transaction annotation to service impl class,
*/
public static void genStackBatch(Map<String, String> models, boolean includeService, boolean serviceTransaction) {
for (String modelName : models.keySet()) {
genStack(modelName, includeService, serviceTransaction, models.get(modelName));
}
}
/**
* generate class fullname,
*
* @param subPackage
* @param modelName
* @return
*/
public static String genFullName(String subPackage, String modelName) {
return genFullName(subPackage, modelName, "");
}
/**
* generate class fullname,
*
* @param subPackage
* @param modelName
* @param suffix
* @return
*/
public static String genFullName(String subPackage, String modelName, String suffix) {
return new StringBuilder().append(basePackage).append(packageSeparator).append(subPackage).append(packageSeparator).append(modelName).append(suffix)
.toString();
}
/**
* generate source file path,
*
* @param subPkgPath
* @param modelName
* @return
*/
public static String genFullPath(String subPkgPath, String modelName) {
return genFullPath(subPkgPath, modelName, "");
}
/**
* generate source file path,
*
* @param subPkgPath
* @param modelName
* @param suffix
* @return
*/
public static String genFullPath(String subPkgPath, String modelName, String suffix) {
return new StringBuilder().append(tmpSourceFolderBaseLocation).append(basePkgPath).append(pkgPathSeparator).append(subPkgPath).append(pkgPathSeparator)
.append(modelName).append(suffix).append(sourceSuffix).toString();
}
/**
* clean tmp location,
*
* @throws IOException
*/
public static void initTmp() throws IOException {
File tmp = new File(tmpSourceFolderBaseLocation);
if (!tmp.exists()) { // create if not exists,
tmp.mkdirs();
} else { // clean if exists,
FileUtils.cleanDirectory(tmp);
}
}
/**
* <p>
* move generated code into source folder,
* </p>
* <p>
* Warning: this will override existing code, so, be careful!
* </p>
*/
public static void merge() {
File originalFile = new File(tmpSourceFolderBaseLocation + basePkgPath);
File targetFile = new File(actualSourceFolderBaseLocation + basePkgPath);
try {
// filter - java file,
IOFileFilter javaSuffixFilter = FileFilterUtils.suffixFileFilter(".java");
IOFileFilter javaFiles = FileFilterUtils.and(FileFileFilter.FILE, javaSuffixFilter);
// filter - dir or java file,
FileFilter filter = FileFilterUtils.or(DirectoryFileFilter.DIRECTORY, javaFiles);
FileUtils.copyDirectory(originalFile, targetFile, filter);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// String modelName = "LoginHistory";
// String actionRootMappingPath = "/loginHistory";
// genStack(modelName, true, false, actionRootMappingPath);
Map<String, String> models = new HashMap<String, String>();
models.put("AdminAccount", "/adminAccount");
models.put("CustomerAccount", "/customerAccount");
models.put("Role", "/role");
genStackBatch(models, true, true);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With