This is my first cut at separating the data access layer out of my very old spaghetti codebase.
I have tried to keep this as simple as possible, it is for a small project, so I am not really interested in learning Spring or Hibernate or other framework, and I don't mind writing a bit of SQL.
I basically have a 1-to-1 relation of database table to business object.
This is the Data Access Layer
package dataaccesslayer;
import dbutils.handlers.IgnoreUnderscoreBeanHandler;
import dbutils.handlers.IgnoreUnderscoreBeanListHandler;
import racemanager.businessobjects.Progression;
import org.apache.commons.dbutils.QueryRunner;
import shared.SLDbHelper;
import java.sql.SQLException;
import java.util.List;
public class DalProgression {
private static final String SQL_SELECT_ONE =
" SELECT * "
+ " FROM progressions "
+ " WHERE id=? ";
private static final String SQL_SELECT_ALL =
" SELECT * "
+ " FROM progressions "
+ " ORDER BY name ASC ";
public static Progression selectOne(int id) {
QueryRunner qr;
Progression progression;
try {
qr = new QueryRunner(SLDbHelper.getInstance().getDataSource());
progression = qr.query(SQL_SELECT_ONE, new IgnoreUnderscoreBeanHandler<Progression>(Progression.class), id);
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
return progression;
}
public static List<Progression> selectAll() {
QueryRunner qr;
List<Progression> progressions;
try {
qr = new QueryRunner(SLDbHelper.getInstance().getDataSource());
progressions = qr.query(SQL_SELECT_ALL, new IgnoreUnderscoreBeanListHandler<Progression>(Progression.class));
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
return progressions;
}
}
This is the related Business Object
package businessobjects;
import java.util.Date;
public class Progression {
private int id;
private String name;
private Date modified;
private Date created;
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 Date getModified() {
return modified;
}
public void setModified(Date modified) {
this.modified = modified;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
@Override
public String toString() {
return this.getName();
}
}
Usage example
int id = 10;
Progression p = DalProgression.selectOne(id);
List<Progressions> pList = DalProgression.selectAll();
Fire away and shoot holes in my code, so I can improve it.
One thing I am not sure about is when I do joins should I have a separate business object for the result, and as such as corresponding data access layer class to perform those queries?
1 Answer 1
How small is small? Hibernate, JPA and so on would help you get rid of a lot of boilerplate code.
That said.
I'd change
selectOne
toselectById
, similarly for other querying functions, which I'm sure you'll get a lot of, or in general find a good, consistent naming scheme for those functions.Keep in mind that
int
has a particular size if you already have a large legacy database.- If possible remove all the
statics
and makeDalProgression
a real object (so you can set and reuseQueryRunner
/SLDHelper
and create better tests). - Did I mention tests. (Legacy == ) please write tests. It honestly helps so much.
- In order to do that get rid of the
SLDHelper
singleton, - which is not a good name, since it has both an unknown acronym in it (at least to me, otherwise you'd have to explain the name) and additionally
Helper
, which can also mean everything (the acronym goes forDalProgression
as well, less so since you've mentioned Data Access Layer; still I'd say something likeProgressionRepository
,ProgressionStore
or so would be better). If possible I'd just avoid such names. - Move the exception conversion into a separate class so you can reuse that, even if you have to create a wrapper for the QueryRunner.
- You could also just directly return from inside the
try
block and initialise the variables with their values directly, i.e.try { QueryRunner qr = ...; return qr.query(...); } catch ...
. - I'd also create a
SQLRuntimeException
or so in order tocatch
exactly those later instead of grepping forRuntimeException
. - Have a plan for transactions in your API, unless you already have, that is.
IgnoreUnderscoreBeanHandler
is already generic, there are options (e.g. via reflection) to get rid of the class literal argument.- Unless you have a specific use for that particular
toString
method I wouldn't use that, rather have something to identify the object in a stacktrace, let's say, theid
("<Progression id=42 name=whatever>"
). - Thinking about it, if you have the ID and the timestamps in all business objects, create an abstract base class or an interface to capture that notion.
For the joins, I guess that unless you have extra data in join tables, creating separate business objects isn't necessary. So I'd imagine it would be easier to understand if you just have methods List<Foo> selectFooForProgression(Progression)
or Map<Foo, Progression> selectFoosForProgressions(List<Progression>)
in the DalProgression
class, basically where you start the join from.
-
\$\begingroup\$ So for point 3 & 5. You would expect the usage to look like this DalProgression dal = new DalProgression(dataSource); dal.selectById(id); \$\endgroup\$bumperbox– bumperbox2014年11月06日 01:22:27 +00:00Commented Nov 6, 2014 at 1:22
-
\$\begingroup\$ Can you please elaborate on point 6, I don't understand what you mean by "a really opaque name" \$\endgroup\$bumperbox– bumperbox2014年11月06日 01:24:27 +00:00Commented Nov 6, 2014 at 1:24
-
\$\begingroup\$ Yes, I mean my point of view is oriented along the lines of Spring/DI, so even if you don't use that, I'd rather construct those objects in one place instead of having
static
methods. I'm updating point 6/7. \$\endgroup\$ferada– ferada2014年11月06日 09:48:33 +00:00Commented Nov 6, 2014 at 9:48