FFL2:FFL2 Main/Hibernate Reverse Engineering/Hibernate Reverse Engineering Example

From FFL Wiki
Jump to navigation Jump to search

In this example we are adding views for real league positions to the database and creating the associated classes and mappings.

When we run a Hibernate reverse engineer it will reverse in both tables and views. However our life will be made easier by creating dummy tables first, then reverse engineering, then replacing them with views of the same name. The reason for this is that we can then define the primary keys and foreign key associations, without these the reverse engineer wouldn't give us exactly what we wanted and we'd be left with manual editing to do. In addition the types of the number would be incorrect (we get things like BigDecimal(45, 0) ).

Define the table

We will add a table as follows

 create table RealLeagueStanding (
	idRealClub        int(11) unsigned not null,
	seasonStartYear   int(11) unsigned not null,
	idRealDivision    int(11) unsigned not null,
	played            int(3) unsigned not null,
	won               int(3) unsigned not null,
	drawn             int(3) unsigned not null,
	lost              int(3) unsigned not null,
	goalsFor          int(3) unsigned not null,
	goalsAgainst      int(3) unsigned not null,
	homeWon           int(3) unsigned not null,
	homeDrawn         int(3) unsigned not null,
	homeLost          int(3) unsigned not null,
	homeGoalsFor      int(3) unsigned not null,
	homeGoalsAgainst  int(3) unsigned not null,
	awayWon           int(3) unsigned not null,
	awayDrawn         int(3) unsigned not null,
	awayLost          int(3) unsigned not null,
	awayGoalsFor      int(3) unsigned not null,
	awayGoalsAgainst  int(3) unsigned not null,
	goalDifference    int(3) not null,
	points            int(3) not null,
	primary key (idRealClub, seasonStartYear),
	CONSTRAINT `temp1rls` FOREIGN KEY (`idRealClub`) REFERENCES `RealClub` (`idRealClub`),
	CONSTRAINT `temp2rls` FOREIGN KEY (`seasonStartYear`) REFERENCES `FantacySeason` (`seasonStartYear`),
	CONSTRAINT `temp3rls` FOREIGN KEY (`idRealDivision`) REFERENCES `RealDivision` (`idRealDivision`)
) ENGINE=InnoDB;

Some points to note:

  • By specifying the columns as not null we will get int primitives in the generated Java. If we did not specify 'not null' we would get Integer objects as variables of this type can be null.
  • The constraints will give cause associations to the relevant classes to be generated.

Run the Reverse Engineer

Run it using 'Run | Hibernate Code Generation... | Reverse Engineer FFL Model from DB'. It will take a minute or two and then you should find all the generated code under the fantacycore/hibernate_gen_src folder.

Merge in the Changes

In the Eclipse 'Java' perspective open up the 'Navigator' view (use 'Window | Show View' if required). We will use the navigator view as it just deals with files and doesn't try to do anything clever.

Copy the following files over directly from the hibernate_gen_src/uk/org/fantacy/model/ directory to the src/uk/org/fantacy/model/ directory as they are new:

  • RealLeagueStanding.java
  • RealLeagueStandingId.java
  • RealLeagueStanding.hbm.xml

We will also need to merge the following files which will be updated as the RealClub and FantacySeason classes will now link to RealLeagueStanding:

  • FantacySeason.java
  • FantacySeason.hbm.xml
  • RealDivision.java
  • RealDivision.hbm.xml
  • RealClub.java
  • RealClub.hbm.xml

We will merge these rather than copy them straight across to make sure we don't overwrite any changes we may have made previously. Fortunately merging is simple in Eclipse, click on both files in the navigator while holding Ctrl then right-click and choose 'Compare with | Each other'.

Merge FantacySeason.java

Comparing the FantacySeason.java files we can see that some of the diffs relate to adding the link to RealLeagueStanding and we can copy them all across. However we don't want to lose the code that was added previously to link to FantacyPlayerSeasonSummary. This isn't in the newly generated files because the constraints don't actually exist in the database as this class is based on a view (or more accurately a table with '_Table' at the end of it's name built from a view). Anyway, we just highlight each diff which we want to bring across and use the 'Copy current change from left to right' button to copy it over. The diffs we want are

  • New member variable for the association
	private Set<RealLeagueStanding> realLeagueStandings = new HashSet<RealLeagueStanding>(
			0);
  • Constructor (don't think this is really important, we don't use this constructor)
			Set<RealLeagueStanding> realLeagueStandings,

and

		this.realLeagueStandings = realLeagueStandings;
  • Getter and Setter
	public Set<RealLeagueStanding> getRealLeagueStandings() {
		return this.realLeagueStandings;
	}

	public void setRealLeagueStandings(
			Set<RealLeagueStanding> realLeagueStandings) {
		this.realLeagueStandings = realLeagueStandings;
	}

Having done all these merges, save the file under fantacycore/src while still in the diff editor. At this point the diffs will update and you should see that the only diffs now are extra code under fantacycore/src which has been added in the past.

Merge FantacySeason.hbm.xml

The diff here is a bit more complicated. That is because the new version of Hibernate tools seems to be add extra information to the <set> element. None of this is required and I'm not really sure why it is adding it. For example

lazy="true"

is the default

table="FantacyCompetition"

is just repeating information in the existing FantacyCompetition.hbm.xml file and

fetch="select"

is, I'm pretty sure, the default as well. So we'll just copy the association we want across and delete all the unnecessary attributes of the <set> tag.

We will add the following after the definition of the fantacyWeeks association (or wherever else, the order of the set definitions isn't important).

        <set name="realLeagueStandings" inverse="true">
            <key>
                <column name="seasonStartYear" not-null="true" />
            </key>
            <one-to-many class="uk.org.fantacy.model.RealLeagueStanding" />
        </set>

Merge RealDivision.java

This is almost identical to the merge for FantacySeason.java above. We will add the following

  • New member variable for the association
	private Set<RealLeagueStanding> realLeagueStandings = new HashSet<RealLeagueStanding>(
			0);
  • Constructor (again I don't think this is really important, we don't use this constructor)
			, Set<RealLeagueStanding> realLeagueStandings

and

		this.realLeagueStandings = realLeagueStandings;
  • Getter and Setter
	public Set<RealLeagueStanding> getRealLeagueStandings() {
		return this.realLeagueStandings;
	}

	public void setRealLeagueStandings(
			Set<RealLeagueStanding> realLeagueStandings) {
		this.realLeagueStandings = realLeagueStandings;
	}

Merge RealDivision.hbm.xml

Similar to the merge for FantacySeason.hbm.xml, we just add the following just before the closing <class/> tag (again we remove the unnecessary attributes of the set tag).

        <set name="realLeagueStandings" inverse="true">
            <key>
                <column name="idRealDivision" not-null="true" />
            </key>
            <one-to-many class="uk.org.fantacy.model.RealLeagueStanding" />
        </set>


Merge RealClub.java

Exactly equivalent to the two previous examples

Merge RealClub.hbm.xml

Exactly equivalent to the two previous examples, just add

        <set name="realLeagueStandings" inverse="true">
            <key>
                <column name="idRealClub" not-null="true" />
            </key>
            <one-to-many class="uk.org.fantacy.model.RealLeagueStanding" />
        </set>


Add data access methods to load the new objects

We won't want to be loading the new RealLeagueStanding objects through their primary keys, we will want to load all the objects for a given season and division at once. We add a new interface method declaration to FantacyDAO.java as follows (tip press Ctrl-Shift-O to automatically pull in the required imports)

	public List<RealLeagueStanding> getRealLeagueStandings(
			RealDivision realDivision, FantacySeason fantacySeason);

We add the implementation of this method in FantacyHibernateDAO as follows

	public List<RealLeagueStanding> getRealLeagueStandings(
			RealDivision realDivision, FantacySeason fantacySeason) {
		Query qry = this.getSession().createQuery(
				"from RealLeagueStanding rls " +
				"where rls.fantacySeason = :fantacySeason " +
				"and rls.realDivision = :realDivision " +
				"order by rls.points desc, rls.goalDifference " +
				"desc, rls.goalsFor desc, rls.goalsAgainst desc");
		
		qry.setEntity("realDivision", realDivision);
		qry.setEntity("fantacySeason", fantacySeason);
		
		List<RealLeagueStanding> ret = getQueryList(qry);
		return ret;
	}

Replace the table with a view

The table we created earlier has now served its purpose of simplifying the reverse engineering. It's now time to drop that and replace it with a view.

  • drop the table as follows
drop table if exists RealLeagueStanding;
  • create the view as follows (a couple of helper views also required)
CREATE OR REPLACE VIEW RealMatchWithScore AS
SELECT
	*,
	sum(CASE when (home = 1 AND idGoal is not null and not ownGoal)
		or (home = 0 AND idGoal is not null and ownGoal)
		then 1
		else 0
		END) as homeScore,
	sum(CASE when (home = 0 AND idGoal is not null and not ownGoal)
		or (home = 1 AND idGoal is not null and ownGoal)
		then 1
		else 0
		END) as awayScore
FROM
	RealMatch
	LEFT JOIN RealAppearance using (idRealMatch)
	LEFT JOIN Goal using (idRealMatch, idPlayer)
WHERE
	idRealMatchStatus = 3
GROUP BY
	idRealMatch;


CREATE OR REPLACE VIEW RealClubResults AS
SELECT
	seasonStartYear,
	idRealDivision,
	idRealMatch,
	idRealClub_home as idRealClub,
	1 as home,
	homeScore as goalsFor,
	awayScore as goalsAgainst,
	homeScore > awayScore as won,
	homeScore = awayScore as draw,
	homeScore < awayScore as loss,
	if ( homeScore > awayScore , 3 , if ( homeScore = awayScore , 1 , 0)) as pts
FROM 
	RealMatchWithScore
UNION
SELECT
	seasonStartYear,
	idRealDivision,
	idRealMatch,
	idRealClub_away as idRealClub,
	0 as home,
	awayScore as goalsFor,
	homeScore as goalsAgainst,
	awayScore > homeScore as won,
	awayScore = homeScore as draw,
	awayScore < homeScore as loss,
	if ( awayScore > homeScore , 3 , if ( homeScore = awayScore , 1 , 0)) as pts
FROM 
	RealMatchWithScore;


CREATE OR REPLACE VIEW RealLeagueStanding AS
SELECT
	idRealClub,
	seasonStartYear,
	idRealDivision,
	count(distinct idRealMatch) as played,
	sum(won) as won,
	sum(draw) as drawn,
	sum(loss) as lost,
	sum(goalsFor) as goalsFor,
	sum(goalsAgainst) as goalsAgainst,
	sum(if(home,won,0)) as homeWon,
	sum(if(home,draw,0)) as homeDrawn,
	sum(if(home,loss,0)) as homeLost,
	sum(if(home,goalsFor,0)) as homeGoalsFor,
	sum(if(home,goalsAgainst,0)) as homeGoalsAgainst,
	sum(if(not home,won,0)) as awayWon,
	sum(if(not home,draw,0)) as awayDrawn,
	sum(if(not home,loss,0)) as awayLost,
	sum(if(not home,goalsFor,0)) as awayGoalsFor,
	sum(if(not home,goalsAgainst,0)) as awayGoalsAgainst,
	sum(goalsFor) - sum(goalsAgainst) as goalDifference,
	sum(pts) as points
FROM 	
	RealClubResults results
GROUP BY 
	seasonStartYear, 
	idRealDivision, 
	idRealClub;